Entity-Framework. Testing that SaveChanges is present and called in the correct place in a method

c# entity-framework mocking unit-testing

Question

I have a class like the following, that I want to unit test:

public class AddUserCommand
{
    IDbContext dbContext;

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

    public void Execute()
    {
        dbContext.Users.Add(new User());
        dbContext.SaveChanges();
    }
}

Ultimately I need to test whether the Execute method persists the new User to the database when using a real sql database connection. But for my unit tests I obviously want to use some kind of mock object. In my tests, I can make a mock IDbContext which mimics the behaviour, and it all works. I can test that the mock context contains the new user after the Execute method has been run.

My problem is that when using the mock context, the test will pass if I do not call the SaveChanges method. This is because the mock context does not need to make an sql query to actually persist the data. It 'persists' without the call to SaveChanges because the Users collection represents the persistent store.

In order to check that SaveChanges is called, many online sources (for example: http://msdn.microsoft.com/en-us/library/ff714955.aspx and http://msdn.microsoft.com/en-gb/data/dn314431.aspx) say to add something like this to the mock context:

public class MockDbContext : IDbContext
{
    boolean saved;
    public void SaveChanges {
        saved = true;
    }
}

And then test if the saved variable is true after the Execute method is called. However, what I find lacking in this approach is that such a test will pass if the Execute method did this:

public void Execute()
{
    dbContext.SaveChanges();
    dbContext.Users.Add(new User());
}

Which of course would not save any changes as it is done too early. I believe that mocking frameworks like RhinoMocks allow you to test the order of method calls to the mock context, but I have also read that this is not best practice (you should test the result, not the minutae of the implementation).

The problem is that the mock context does not exactly replicate what the real DbContext will do.

So my question is: is there a standard way to mock an entity framework DbContext in such a way that any additions or deletions of objects are only committed to the mock when SaveChanges is called? Or is this not something that is generally tested for?

1
7
2/4/2014 2:23:23 PM

Popular Answer

You should be able to do this using the Moq framework:

// 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);

Following the comments about this answer, it seems that some people are missing the point of a unit test and the purpose of using abstractions within your code.

What you are doing here is verifying the behaviour of the AddUserCommand and that is all - you are confirming that the AddUserCommand class is adding a user and saving the changes on the context.

The reason to use the IDbContext interface is so that you can test the AddUserCommand class in isolation without having a database available in a known state. You don't need to test the implementation of the real DbContext because that should have it's own unit tests that cover that in isolation too.

You may also want to create an integration test where you would use the real DbContext and confirm that a record goes into the database but that is not what a unit test does.

13
2/4/2014 4:00:49 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow