3

我需要帮助测试以下代码

public virtual void Update(T entity)
    {
        if (entity == null)
        {
            throw new ArgumentNullException("entity");
        }

        int iretries = 0;
        bool success = false;

        do
        {
            try
            {
                this.context.SaveChanges();
                success = true;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                // Get the current entity values and the values in the database
                // as instances of the entity type
                var entry = ex.Entries.Single();
                var databaseValues = entry.GetDatabaseValues();

                // Choose an initial set of resolved values. In this case we
                // make the default be the values currently in the database: StoreWins
                object resolvedValues = ResolveConcurrency(databaseValues.ToObject());

                // Update the original values with the database values and
                // the current values with whatever the user choose.
                entry.OriginalValues.SetValues(databaseValues);
                entry.CurrentValues.SetValues(resolvedValues);

                // give up after n retries
                if (++iretries == NUMBER_OF_CONC_RETRIES)
                    throw;
            }
            catch (Exception)
            {
                //rethrow 
                throw;
            }
        } while (!success);
    }

我想对DbUpdateConcurrencyException分支进行单元测试。

因此,一个简单的测试场景是:

  • 创建一个新的DbUpdateConcurrencyException
  • 模拟SaveChanges抛出上述异常
  • 验证是否SaveChanges被调用了一些NUMBER_OF_CONC_RETRIES
  • 断言该Update方法重新抛出异常

在当前状态下,无法测试上述测试场景,我无法模拟异常以包含IEnumerable<DbEntityEntry>单个DbEntityEntry; 我不能嘲笑GetDatabaseValues(), 等等。

一个简单的解决方案是插入一个新的抽象层;假设使用一个接口来抽象当前位于 catch 块中的整个代码,并提供一个什么都不做的模拟。

但是我最终会遇到想要测试该接口的实现的情况,并且最终会遇到与上述相同的问题。我怎样才能嘲笑DbUpdateConcurrencyException,GetDatabaseValues

我正在使用 moq 进行模拟。

谢谢您的意见

4

1 回答 1

1

如果您无法模拟某些内容,则必须将其隐藏在可以在测试中模拟或覆盖的其他内容后面。您的测试实际上不需要使用所有这些东西来加载值并将它们设置在条目中 - 这完全取决于 EF,并且在模拟上下文时您不会对其进行测试,因为这意味着重新实现 EF 的逻辑SaveChanges。您需要做的就是:

catch (DbUpdateConcurrencyException ex) {
    RefreshValues(ex);

    // give up after n retries
    if (++iretries == NUMBER_OF_CONC_RETRIES)
        throw;
}

方法在哪里,您RefreshValues可以protected virtual通过提供类的测试版本(甚至可以使用 Moq 实现)或通过从此类继承测试并直接在测试类中覆盖方法来在测试中覆盖它。

要设置 Moq,您需要公开 SaveChanges 方法的上下文接口:

var contextMock = new Mock<IContext>();
contextMock.Setup(m => m.SaveChanges())
           .Callback(m => throw new DbUpdateConcurrencyException());

如果您需要测试它是否适用于几次抛出,您需要在测试中保留计数器并在回调中使用它来决定是否抛出。

于 2012-09-07T09:12:08.450 回答