We continue our series of experiments using the infamous Nerd Dinner application as our reference implementation. Last time we added Dependency Injection using StructureMap. This time we will look at unit testing with Moq. Like the dependency injection last week, the unit testing strategies used on Nerd Dinner work fine for an application the size of Nerd Dinner. However, my goal in this series of experiments is to try to understand how to apply various alternative technologies and how these technologies impact larger real-world applications. The Nerd Dinner application is relatively small and it already has about 100 unit tests. What happens when there are 500 or a 1000 units tests? I realize that like lines of code, the number of units tests by itself is meaningless. However, assuming you continue to follow your methodology, as an application gets larger and more complicated, both the number of lines of code and the number of unit tests go up.
Moq
Moq is a mocking framework used in unit testing. Several mocking frameworks are available, and a interesting comparisons are available here and here. If you are familiar with a mocking frameworks, you should consider using what you know. I am going to discuss the implications of using mocks versus fakes in MVC applications, but I am not going to cover anything that is not supported by most leading mock frameworks. Let’s look at a test from the DinnerControllerTest.
[TestMethod]
public void DetailsAction_Should_Return_View_For_Dinner() {
// Arrange
var controller = CreateDinnersController();
// Act
var result = controller.Details(1);
// Assert
Assert.IsInstanceOfType(result, typeof(ViewResult));
}
First, in the Arrange section, the test requests a controller instance from a central CreateDinnersController(). I am torn on this practice. Yes, this supports a DRY principle. Changes to the controller portion of the test can be made in one place. On the flip side, in unit tests, I am more concerned with unit of work, keeping the test simple, and only setting up what needs to be set-up for the particular test in question. With a centralize factory method for test, the method tends to get complicated by trying to support all the conditions of all the tests. In the case above, these complications are moved to fakes, but the complications exist just the same. While the complications can generally be handled by the mocking framework, these conditions are programmed by humans working under deadlines. One of the fundamental principles of JIT manufacturing and quality control is to make it easier to do things right than to do them wrong. On the other side of this double-edged sword, if you put your controller instantiation in each test, and you add a new dependency to a controller, you now have many places to change your test code – maybe.
Let’s look at the CreateDinnersController() method.
DinnersController CreateDinnersController() {
var testData = FakeDinnerData.CreateTestDinners();
var repository = new FakeDinnerRepository(testData);
return new DinnersController(repository);
}
What should a controller be?
In many tutorials of the MVC framework, the model contains the data, the view presents data, and the controller contains the business logic to prepare the data for the view. When reading this discussion on fakes vs. mocks, keep in mind this is not my view of MVC.
I sometimes have a flexible morality when it comes to code, but I have some strong beliefs on MVC. I believe in thin Controllers; they are traffic cops, if you play soccer, they are the center midfielder responsible for distributing the ball to the appropriate players. I also believe in view models that exactly match the view. If I am to use fakes, I will more likely use them on models and view models to assure the delivery of well formed objects. However, on controller actions, I want to know the controller is behaving as expected. |
This method uses a static method FakeDinnerData.CreateTestDinners() to generate test data. It then creates a FakeDinnerRepository to inject into the DinnersController. Fakes are not mocks, and Martin Fowler and his team provide a concise explanation of the difference. In over simplified terms, Fakes are used to test state, mocks are used to test behavior.
In NerdDinner, the FakeDinnerRepository is simple, however, fakes can become more complicated. To pass a test, the developer now not only needs to worry that the controller is functioning properly but also that the fake is delivering correct and well formed results to the controller. When testing an action of the controller, I want to know the controller is calling a repository with correct parameters and the correct number of times – that it behaves as expected. I don’t care if my repository (or fake repository) are behaving properly; I have separate unit test for that.
Here is where I will start with Moq. Despite my concerns with the centralized CreateDinnersController method, I will keep it. I will also keep the FakeDinnerData class. Test data is not needed in our first test, but it will be needed later. I don’t want to create test data code any more than I need to. I will replace the FakeDinnerRepository with a mock repository. The CreateDinnersController method now becomes:
DinnersController CreateDinnersController(string userName = null) {
var testData = FakeDinnerData.CreateTestDinners();
_mockDinner = new Mock<Dinner>();
_mockDinnerRepo = new Mock<IDinnerRepository>();
_mockDinnerRepo.Setup(dr => dr.GetDinner(It.IsInRange(1, 1, Range.Inclusive)))
.Returns(_mockDinner.Object);
return new DinnersController(_mockDinnerRepo.Object);
}
Is this more lines of code? Depends how much code was in the FakeDinnerRepository. In this method, I am creating a mock Dinner and a mock IDinnerRepository. I then describe the behavior I want this repository to have – if the repository’s GetDinner method is called with a parameter in the range of 1 to 1 (inclusive), then return the mock dinner as a Dinner object. _mockDinner is a mock object, but _mockDinner.Object returns a Dinner object. Our new controller is then injected with our mock IDinnerRepository. Our first test, shown above, remains the same. However, we can now add an additional test to verify that the controller behaved as expected. Specifically, did the controller call dinnerRepository.GetDinner with an integer parameter, and did it call .GetDinner once and only once. We add the following Assert to our test:
_mockDinnerRepo.Verify(x => x.GetDinner(It.IsAny<int>()),Times.Exactly(1));
The next test “DetailsAction_Should_Return_NotFoundView_For_BogusDinner” sends a bad dinnerID to the Details action of the Dinner controller. The controller should still behave properly as above, but our mock will not. The Controller expects the repository to return null when GetDinner is given a bad dinnerID, but we only told our mock how to behave when the dinner ID is 1. Let’s tell the mock a little more about is behavior. In the CreateDinnersController, we add the following setup statement:
_mockDinnerRepo.Setup(dr => dr.GetDinner(It.IsInRange(2, 1000, Range.Inclusive)))
.Returns<Dinner>(null);
Now when .GetDinner is called with any integer value between 2 and 100 (inclusive), null will be returned. And like the first test, we can add our Verify to confirm the controller faithfully called .GetDinner once and only once. Now for the third test - don’t worry, I am not going through all 96 unit tests one at a time. Just where we are adding something new. This test checks the controller’s Edit method, but the controller comes from a CreateDinnersControllerAs(string) method. The Edit action requires the user identity from the HttpContext, and then it calls the CreateDinnerController method to create the controller as before. Extending the centralized controller creation in this way is one way to mitigate my concerns of overly complicated centralized methods.
[TestMethod]
public void EditAction_Should_Return_View_For_ValidDinner() {
// Arrange
var controller = CreateDinnersControllerAs("SomeUser");
// Act
var result = controller.Edit(1) as ViewResult;
// Assert
Assert.IsInstanceOfType(result.ViewData.Model, typeof(Dinner));
}
The NerdDinner application already uses Moq, and it uses it in the CreateDinnerControllerAs method to create the ControllerContext. We will leave this unchanged, but our mock dinner now needs to know how to behave when the .IsHostedBy method is called with a user’s name. We need to get the user’s name to where we are setting up the mock’s behavior, but I don’t want to have to pass in a user’s name into the CreateDinnerController method on all of these test where I don’t need it. To solve this, I am going to use a new feature of C# 4.0, the optional parameter. I have to admit, this is one feature I like from VB. Yes, I said it, I used to program in VB, but that’s nothing. I used to program in COBOL, Pascal, Assembler and APL. Anyone out there ever program matrix math in APL – fugly! Our CreateDinnerController becomes…
DinnersController CreateDinnersController(string userName = null) {
var testData = FakeDinnerData.CreateTestDinners();
_mockDinner = new Mock<Dinner>();
_mockDinner.Setup(d => d.IsHostedBy("SomeUser")).Returns(true);
_mockDinnerRepo = new Mock<IDinnerRepository>();
_mockDinnerRepo.Setup(dr => dr.GetDinner(It.IsInRange(1, 1, Range.Inclusive))).Returns(_mockDinner.Object);
_mockDinnerRepo.Setup(dr => dr.GetDinner(It.IsInRange(2, 1000, Range.Inclusive))).Returns<Dinner>(null);
return new DinnersController(_mockDinnerRepo.Object);
}
Two changes. First, the userName was added as an optional input parameter to our controller creator. Second, our mock dinner is told how to behave when it’s .IsHostedBy method is called and is passed the string “SomeUser”. We can also add a second Verify to our test to confirm that the IsHostedBy method is called once and only once. The fourth test is almost the same, but we add a “bad user” behavior to our mock dinner with the following line:
_mockDinner.Setup(d => d.IsHostedBy("SomeOtherUser"))
.Returns(false);
The next few tests offer some variation to the above, but really offer nothing new relating to mocking until we get to “IndexAction_Should_Return_View”. The index action returns a list of dinners. Remember FakeDinnerData.CreateTestDinners() in our creator that we kept but were not using, well now we want it. The CreateTestDinners returns a List<Dinner> of test dinner objects, and this list is assigned to our testDinners variable. To support the Index action tests, we add the following line to our mock IDinnerRepository set-up:
_mockDinnerRepo.Setup(dr => dr.FindUpcomingDinners())
.Returns(testDinners.AsQueryable());
Let’s look at what we’ve done. We eliminated the FakeDinnerRepository, we’ve added several behaviors to our CreateDinnerController method, and we added some additional asserts (Verify) to our tests to confirm Controller behavior. We are creating a list of test dinners on every test (in CreateDinnerController) even if we don’t use them, and we kept the existing method that sets up the controller context when needed. What happens if you extrapolate these test strategies out to a medium or large application?
What happens with a controller with 200 tests and 4 injected repositories? The CreateDinnerController method becomes more complex. This complexity can be managed, but we are making it easier to make a mistake. Also, we may very likely be creating a large amount of test data and not always using it. We can mitigate the complexity by moving the controller creation into each individual test so we are only creating the test data and behaviors needed for each individual test. However, this solution has its own set of problems. Let’s say a 5th repository needs to be injected into the controller, or the behavior of a repository method changes, we would now have many places to change code. In my case, I think I’m smart, and I know I’m lazy, so I almost always go with the centralized solution. However, I’m torn. There has to be a better way. Next up, AutoMocking.
d19482ad-19b1-4e18-988b-2a2e24c53731|4|3.5