Unit testing MongoDB in C# part 4: the tests, finally
More than a year. Wow, that’s a lot, even for me! In the last episode of this series, we discussed about how to create the Factories for our Repositories. I guess now it’s time to put a use to all those interfaces and finally see how to unit test our MongoDB repositories 🙂
Remember: we are not testing the driver here. The MongoDB team is responsible for that. Not us.
What we have to do instead is to make sure all our classes follow the SOLID principles and are testable. This way we can create a fake implementation of the low-level data access layer and inject it in the classes we have to test. Stop.
Let’s have a look at the code:
using Xunit; | |
using Moq; | |
public class CreateUserHandlerTests | |
{ | |
[Fact] | |
public async Task should_create_user_when_command_valid() | |
{ | |
var command = new CreateUser(id: Guid.NewGuid(), username: "loremipsum", email: "lorem@ipsum.com", firstname: "lorem", lastname:"ipsum"); | |
var mockRepo = new Mock<IRepository<Infrastructure.Entities.User>>(); | |
var mockDbContext = new Mock<IDbContext>(); | |
mockDbContext.Setup(db => db.Users).Returns(mockRepo.Object); | |
var sut = new CreateUserHandler(mockDbContext.Object); | |
await sut.Handle(command); | |
mockRepo.Verify(m => m.InsertOneAsync(It.IsAny<Infrastructure.Entities.User>()), Times.Once()); | |
} | |
} |
In our little example here I am testing a CQRS Command Handler, the one responsible for creating a user. Our handler has an IDbContext as dependency, which being an interface allows us to use the Moq Nuget package to create a fake context implementation.
Also, we have to instruct the mockDbContext instance to return a mock User Repository every time we access the .Users property.
At this point, all we have to do is to create the sut, execute the method we want to test, and Verify() our expectations.
Let’s make a more interesting example now:
using Xunit; | |
using Moq; | |
public class UpdateUserHandlerTests | |
{ | |
[Fact] | |
public async Task should_Update_user_when_command_valid() | |
{ | |
var command = new UpdateUser(id: Guid.NewGuid(), firstname: "lorem", lastname: "ipsum"); | |
var user = new Infrastructure.Entities.User() | |
{ | |
Id = command.UserId | |
}; | |
var mockRepo = new Mock<IRepository<Infrastructure.Entities.UserData>>(); | |
mockRepo.Setup(r => r.FindOneAsync(It.IsAny<Expression<Func<Infrastructure.Entities.UserData, bool>>>())) | |
.ReturnsAsync(user); | |
var mockDbContext = new Mock<IUserServiceDbContext>(); | |
mockDbContext.Setup(db => db.UsersData).Returns(mockRepo.Object); | |
var sut = new UpdateUserHandler(mockDbContext.Object); | |
await sut.Handle(command); | |
mockRepo.Verify(m => m.UpsertOneAsync(It.IsAny<Expression<Func<Infrastructure.Entities.UserData, bool>>>(), It.IsAny<Infrastructure.Entities.UserData>()), Times.Once()); | |
} | |
} |
Now that we have created the user, we may want also to update some of his details. The idea here is to instruct the mockRepo instance to return a specific user every time the FinstOneAsync method is executed.
Again, now we just need to verify the expectations and we’re done!
Note that in this case, we are making an assumption about the inner mechanism of the Handle() method of the UpdateUserHandler class. Personally I tend to stick with Black Box Testing, but sometimes (eg. now) you might be forced to use White Box Testing instead. If you don’t know what I am talking about, there’s a nice article here you may want to read.
Update 13/05/2020:
I’ve added another article, which can be considered as the final missing piece to this Series. Unit tests are fine, but when it comes to connecting the parts, we’re talking of “Integration tests” instead.