unit测试EF DbUpdateConcurrencyException处理

时间:2012-09-07 08:46:01

标签: c# entity-framework unit-testing mocking moq

我需要帮助测试以下代码

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块中的整个代码,并提供一个什么都不做的模拟。

但是当我想要测试该接口的实现时,我最终会遇到这种情况,最终会遇到与上面相同的问题。如何模仿DbUpdateConcurrencyExceptionGetDatabaseValues

我正在使用moq进行嘲弄。

感谢您的输入

1 个答案:

答案 0 :(得分:1)

如果你不能嘲笑某些东西,你必须将它隐藏在其他东西之后,你可以在测试中模拟或覆盖。您的测试实际上不需要使用所有内容来加载值并在条目中设置它们 - 这完全依赖于EF,并且在模拟上下文时您不会测试它,因为这意味着重新实现EF的逻辑{ {1}}。您所需要做的就是:

SaveChanges

其中catch (DbUpdateConcurrencyException ex) { RefreshValues(ex); // give up after n retries if (++iretries == NUMBER_OF_CONC_RETRIES) throw; } 可以是RefreshValues方法,您可以通过提供类的测试版本(甚至可以使用Moq实现此目的)或通过继承此类的测试来覆盖测试并直接在测试类中重写该方法。

要设置Moq,您需要为您的上下文显示SaveChanges方法的接口:

protected virtual

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