designing technology that makes business sense

In our last few posts, we have been looking a various technologies applied against the [Nerd Dinner] application.  We chose Nerd Dinner as our reference application, because it is well known, it is simple, and there is a real-world [functioning application] for comparison. In our earlier posts, we applied StructureMap and Moq.

In my last post I discussed the use of a centralized method in the test class used to instantiate the controller and set-up the mocks.  My concern is that this method can get overly complicated in a larger application when all the set-ups for all the tests are plugged in.  Another concern was creating test data for every test when that test data may only be used in some tests.  The alternative was to instantiate the controller and mocks in each test.  That way only set-ups and test data need for the specific-test are created.  However, this strategy can be even worse on a large project.  If a new dependency is added to a controller, there could be many places to change the test code.  Given these choices, I almost always go with the centralized method.  But these are not the only choices…

Automocking

Automocking capability is provided by the Dependency Injection Framework, or in this example, StructureMap.  We will be using StructureMap’s AutoMocker in our tests.  StructureMap provides an automocker for RhinoMocks and Moq.  The documentation on the StructureMap site for RhinoAutoMocker is better than it is for the Moq component, but Joel Abrahamsson provides as nice intro to MoqAutoMocker.

When we last visited the NerdDinner tests with Moq, we modified the CreateDinnersController to become:

DinnersController CreateDinnersController(string userName = null) {
  _mockDinner = new Mock<Dinner>();
  _mockDinner = FakeDinnerData.CreateMockDinner(_mockDinner);
  _mockDinner.Setup(d => d.IsHostedBy("SomeUser")).Returns(true);
  _mockDinner.Setup(d => d.IsHostedBy("SomeOtherUser")).Returns(false);
  List<Dinner> testDinners = FakeDinnerData.CreateTestDinners();
  _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);
  _mockDinnerRepo.Setup(dr => dr.FindUpcomingDinners())
                 .Returns(testDinners
                 .AsQueryable());
  return new DinnersController(_mockDinnerRepo.Object);
}

In this post, we will drop this method completely, and we will move our Controller creation and mock set-up into each test.  Yes, I said moving all this set-up into every test was bad.  Changes to the controller would result in many changes to our test code.  But I also said there had to be a better way; let’s see this through.  We will use the MoqAutoMocker to create our mocks in each test.  Let’s look at the first test method.

[TestMethod]
public void DetailsAction_Should_Return_View_For_Dinner() 
{
  // Arrange
  _autoMocker = new MoqAutoMocker<DinnersController>();
  _mockDinner = new Mock<Dinner>();
  _mockDinner = FakeDinnerData.CreateMockDinner(_mockDinner);
  Mock.Get(_autoMocker.Get<IDinnerRepository>()).Setup(dr => dr.GetDinner(It.IsInRange(1, 1, Range.Inclusive))).Returns(_mockDinner.Object);
  // Act
  var result = _autoMocker.ClassUnderTest.Details(1);
  // Assert
  Assert.IsInstanceOfType(result, typeof(ViewResult));
  Assert.IsInstanceOfType(((ViewResult)result).ViewData.Model, typeof(Dinner));
  Mock.Get(_autoMocker.Get<IDinnerRepository>()).Verify(x => x.GetDinner(It.IsAny<int>()), Times.Exactly(1));
}

In the arrange section of our test method, MoqAutoMocker creates mock instances of the dependencies of our DinnersController.  As before, we continue to create our mock Dinner, because we still need to tell our mock DinnerRepository to return a dinner object when GetDinner is called with a valid parameter.  The set-up of our mock DinnerRepository has changed.  The Mock.Get(autoMocker.Get<T>()) is the syntax for getting an instance of the mocked object T.  Note this is a little different than the syntax for RhinoAutoMocker as documented on the StructureMap site.  I thank Abrahamson for the help on this syntax.  Also notice that the Act section changed.  Instead of calling an action directly on a controller instance, we are calling _autoMocker.ClassUnderTest to get an instance of the controller.

What did this do for us? Well, instead of calling a centralized method to get an instance of the DinnersController.  I now need to create my Dinner mock and test data within my test, but only on those tests using a Dinner object.  The asserts remain effectively the same.  What happens if the controller ads another dependency (e.g. a EntreeRepository)?  Not much.  the AutoMocker will automatically create the dependent objects as needed.  If I will be using the new dependency in a specific test, I will need to set-up the mock (or fake) to behave as expected – just as I would have to do for any to the testing strategies discussed.  My test set-up is simple, because the test objects only need to be arranged for the specific test I am working on at that time.

The second test becomes even lighter weight, because I do not need to create a Dinner object to return from GetDinner.  As seen below, I am not creating any test data, and I only need to set-up the specific behavior needed for the test.  I keep my test simple.  This makes writing test easy, makes them easier to do right, and it makes it easier to adhere to the methodology.  TDD provides reliable code, and it is arguably faster in the long run.  But TDD is not faster in the short run.  When deadlines or hot fixes come, the methodology can get pushed aside if it is not easy to do things right.  This is Tom Peters 101, keep it agile, and make it easy to do things right.

[TestMethod]
public void DetailsAction_Should_Return_NotFoundView_For_BogusDinner() {
  // Arrange
  var autoMocker = new MoqAutoMocker<DinnersController>();
  Mock.Get(autoMocker.Get<IDinnerRepository>())
      .Setup(dr => dr.GetDinner(It.IsInRange(2, 1000, Range.Inclusive)))
      .Returns<Dinner>(null);
  // Act
  var result = autoMocker.ClassUnderTest.Details(999);
  // Assert
  Assert.IsInstanceOfType(result, typeof(FileNotFoundResult));
  Assert.AreEqual("No Dinner found for that id", ((FileNotFoundResult)result).Message);
  Mock.Get(autoMocker.Get<IDinnerRepository>())
      .Verify(x => x.GetDinner(It.IsAny<int>()), Times.Exactly(1));
}

This all sounds good, but the third test starts to get a little messy.  In this test, I need test data (the Dinner as in test 1), and I need a user context.  Previously, the user context was created by calling CreateDinnersControllerAs passing a user name.  This method would mock the HttpContext identity of the user, assign it to the Controller context and then call CreateDinnerController.  The set-up still needs to happen, so I am going to keep this method.  However, I need to change the method to use automocking, and I now the method will return an autoMocker to the test for test-specific set-up.

MoqAutoMocker<DinnersController> CreateDinnersControllerAs(string userName)
{
  var autoMocker = new MoqAutoMocker<DinnersController>();
  var mock = new Mock<ControllerContext>();
  mock.SetupGet(p => p.HttpContext.User.Identity.Name).Returns(userName);
  mock.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
  autoMocker.ClassUnderTest.ControllerContext = mock.Object;
  return autoMocker;
}

This is really not much of a change from the previous method.  I decided to keep the centralized method in this case because the method only adds user context instantiation to the controller automock.  This method does not get more complex as the application and variety of tests grow, and the method is only used for those tests that need a user context.  With this method updated, the third test becomes:

[TestMethod]
public void EditAction_Should_Return_View_For_ValidDinner() {
  // Arrange
  var autoMocker = CreateDinnersControllerAs("SomeUser");
  var mockDinner = new Mock<Dinner>();
  mockDinner = FakeDinnerData.CreateMockDinner(mockDinner);
  mockDinner.Setup(d => d.IsHostedBy("SomeUser"))
            .Returns(true);
  Mock.Get(autoMocker.Get<IDinnerRepository>())
      .Setup(dr => dr.GetDinner(It.IsInRange(1, 1, Range.Inclusive)))
      .Returns(mockDinner.Object);
  // Act
  var result = autoMocker.ClassUnderTest.Edit(1) as ViewResult;
  // Assert
  Assert.IsNotNull(result);
  Assert.IsInstanceOfType(result.ViewData.Model, typeof(Dinner));
  Mock.Get(autoMocker.Get<IDinnerRepository>()).Verify(x => x.GetDinner(It.IsAny<int>()), Times.Exactly(1));
  mockDinner.Verify(x => x.IsHostedBy(It.IsAny<string>()), Times.Exactly(1));
}

This test is more complicated, but it was more complicated than most of the other tests before automocking.  The Edit action does more than most DinnerController actions.  The net result is more lines of code in my tests, but my tests are not dependent on a centralized method complicated by flavors injected by individual tests.  The test are responsible for their own flavors.  Truth be told, I rarely code a test method from scratch.  I usually cut and paste one test to form another, and then I change the new code to fit the new test.  With automocking, repeated code (e.g. var automocker = new MoqAutoMocker<DinnersController>()) is included in my cut and paste.  Then I only need to worry about the conditions and assertions dictated by new test.  If the DiinerController changes, or if the interface of a dependency changes, I don’t care unless I am working on a test that will validate that change.

Next up, we will explorer specifications with MSpec.

This set of experiments applied to NerdDinner has started taking on the feel of a series.  If you have an interest in earlier posts in the series, here are the links.

.Net MVC2 – DependencyInjection with StructureMap

.Net MVC2 – Unit Testing with Moq


Comments

September 9. 2010 00:55

My husband and i both liked your informative post. thankyou.

big tall mens clothing United States

September 16. 2010 02:15

Thanks for the post.Very helpful.

cash for gold toronto United States

September 24. 2010 01:34

This is a really excellent post. The information you have provided are really note worthy. I look forward your next post.

Gregory Despain United States

September 26. 2010 02:04

Be sure not to limit yourself. Several writers restrict themselves to what they think they are able to do. Keep in mind that you can go as far as your mind lets you.

Tony United States

September 28. 2010 06:54

Great blog, but MS software... really? For my avatar costume blog, I'd switch to WP (wordpress) - I ended up having to due to spammers, and I use 'Akismet' now. Try it out!

David Deangelo United States

September 28. 2010 11:35

Be thank you for this share I very interested in your site, I want more information like this. thanks.

Mich Saudi Arabia

September 30. 2010 03:05

Interesting homepage. Can I give you an advice? I think you've got something good in this post. But what if you give a couple links to a page that relates to what you're talking about?

Free PSN Codes United States

October 1. 2010 02:03

Judgement day is inevitable

arcadegames4u.com United States

Comments are closed