实体框架.测试SaveChanges是否存在并在方法中的正确位置调用

Mat*_*yde 7 c# unit-testing entity-framework mocking

我有一个类似下面的类,我想进行单元测试:

public class AddUserCommand
{
    IDbContext dbContext;

    public AddUserCommand(IDbContext context)   
    {
    dbContext = context;
    }

    public void Execute()
    {
        dbContext.Users.Add(new User());
        dbContext.SaveChanges();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我需要测试在使用真正的SQL数据库连接时,Execute方法是否将新用户持久保存到数据库中.但是对于我的单元测试,我显然想要使用某种模拟对象.在我的测试中,我可以创建一个模拟行为的模拟IDbContext,这一切都有效.我可以在运行Execute方法后测试模拟上下文是否包含新用户.

我的问题是,当使用模拟上下文时,如果我不调用SaveChanges方法,测试将通过.这是因为模拟上下文不需要使用sql查询来实际持久化数据.它在没有调用SaveChanges的情况下"持续存在",因为Users集合代表持久性存储.

为了检查SaveChanges是否被调用,许多在线资源(例如:http: //msdn.microsoft.com/en-us/library/ff714955.aspxhttp://msdn.microsoft.com/en-gb/ data/dn314431.aspx)说要在mock环境中添加这样的东西:

public class MockDbContext : IDbContext
{
    boolean saved;
    public void SaveChanges {
        saved = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在调用Execute方法后测试保存的变量是否为true.但是,我发现这种方法缺少的是,如果Execute方法执行此操作,则会传递此类测试:

public void Execute()
{
    dbContext.SaveChanges();
    dbContext.Users.Add(new User());
}
Run Code Online (Sandbox Code Playgroud)

这当然不会保存任何更改,因为它太早完成.我相信像RhinoMocks这样的模拟框架允许你测试模拟上下文的方法调用顺序,但我也读过这不是最佳实践(你应该测试结果,而不是实现的minutae).

问题是模拟上下文并没有完全复制真正的DbContext将要做的事情.

所以我的问题是:是否有一种标准方法来模拟实体框架DbContext,以便在调用SaveChanges时,对象的任何添加或删除都只提交给模拟?或者这不是通常测试的东西?

Tre*_*ley 13

您应该能够使用Moq框架执行此操作:

// Counters to verify call order
int callCount = 0;
int addUser = 0;
int saveChanges = 0;

// use Moq to create a mock IDbContext.
var mockContext = new Mock<IDbContext>();

// Register callbacks for the mocked methods to increment our counters.
mockContext.Setup(x => x.Users.Add(It.IsAny<User>())).Callback(() => addUser = callCount++);
mockContext.Setup(x => x.SaveChanges()).Callback(() => saveChanges = callCount++);

// Create the command, providing it the mocked IDbContext and execute it
var command = new AddUserCommand(mockContext.Object);
command.Execute();

// Check that each method was only called once.
mockContext.Verify(x => x.Users.Add(It.IsAny<User>()), Times.Once());
mockContext.Verify(x => x.SaveChanges(), Times.Once());

// check the counters to confirm the call order.
Assert.AreEqual(0, addUser);
Assert.AreEqual(1, saveChanges);
Run Code Online (Sandbox Code Playgroud)

关于这个答案的评论之后,似乎有些人忽略了单元测试的重点以及在代码中使用抽象的目的.

你在这里做的是验证行为,AddUserCommand这就是全部 - 你正在确认AddUserCommand该类正在添加用户并保存对上下文的更改.

使用该IDbContext接口的原因是,您可以单独测试AddUserCommand该类,而无需在已知状态下提供数据库.你不需要测试真实的实现,DbContext因为它应该有它自己的单元测试,也可以单独覆盖它.

您可能需要创建一个集成测试,你会用实际DbContext并确认记录进入数据库,但就是没有一个单元测试做什么.

  • +1,但我希望有一种方法可以测试更改是否已保存,而不是调用“SaveChanges”。后者感觉像是测试实现细节而不是结果。 (2认同)