Los Techies : Blogs about software and anything tech!

Unit Testing Simple ASP.NET MVC Controllers


I have created enough simple projects using ASP.NET MVC with unit tests to notice a very helpful pattern. The following is a sample of a test fixture using RhinoMocks and NUnit to test a controller.

   1: [TestFixture]
   2: public class AdminControllerTests
   3: {
   4:     private IFacilityRepository facilityRepository;
   5:     private IMeetingRepository meetingRepository;
   6:     private IUserSession userSession;
   7:  
   8:     [SetUp]
   9:     public void SetUp()
  10:     {
  11:         facilityRepository = MockRepository.GenerateMock<IFacilityRepository>();
  12:         meetingRepository = MockRepository.GenerateMock<IMeetingRepository>();
  13:         userSession = MockRepository.GenerateMock<IUserSession>();
  14:     }
  15:  
  16:     [Test]
  17:     public void SaveMeeting_should_call_Add_on_MeetingRepository_if_MeetingId_is_zero()
  18:     {
  19:         // Arrange
  20:         var meetingData = new MeetingData { MeetingId = 0, FacilityId = 0 };
  21:         meetingRepository.Stub(x => x.GetById(0)).Return(new Meeting());
  22:         facilityRepository.Stub(x => x.GetById(0)).Return(new Facility());
  23:         var controller = GetController();
  24:  
  25:         // Act
  26:         controller.SaveMeeting(meetingData);
  27:  
  28:         // Assert
  29:         meetingRepository.AssertWasCalled(x => x.Add(Arg<Meeting>.Is.Anything));
  30:         meetingRepository.AssertWasNotCalled(x => x.Update(Arg<Meeting>.Is.Anything));
  31:     }
  32:  
  33:     [Test]
  34:     public void SaveMeeting_should_call_Update_on_MeetingRepository_if_MeetingId_is_not_zero()
  35:     {
  36:         // Arrange
  37:         var meetingData = new MeetingData { MeetingId = 1, FacilityId = 1 };
  38:         meetingRepository.Stub(x => x.GetById(1)).Return(new Meeting());
  39:         facilityRepository.Stub(x => x.GetById(1)).Return(new Facility());
  40:         var controller = GetController();
  41:  
  42:         // Act
  43:         controller.SaveMeeting(meetingData);
  44:  
  45:         // Assert
  46:         meetingRepository.AssertWasNotCalled(x => x.Add(Arg<Meeting>.Is.Anything));
  47:         meetingRepository.AssertWasCalled(x => x.Update(Arg<Meeting>.Is.Anything));
  48:     }
  49:  
  50:     private AdminController GetController()
  51:     {
  52:         return new AdminController(userSession, meetingRepository, facilityRepository);
  53:     }
  54: }

 

The reason I’m being explicit about the term “simple” in this case is that the controller above that I’m testing doesn’t have much going on. It has some constructor dependencies (like all controllers with dependencies should), but that’s about it. The gist of the pattern is made up of three things:

  1. A SetUp method, via NUnit, runs before each test to create new, fake implementations of the dependencies. In this case I’m using RhinoMocks, but you’re not limited to that (see below). Declaring them at the class level is nice, it allows you to use or ignore them at your leisure.
  2. Creating the GetController() method constrains the creation of a controller to one place. This is good because your dependencies can change by adding more or removing existing dependencies. By creating one place to “new up” the controller, you only have to update one area when dependencies change instead of updating a constructor in each test. This isn’t anything new for unit testing, just a good practice.
  3. Finally, your individual tests can get a new controller and stub/mock/fake the methods of your dependencies at any time during the test method and assert values or verify behavior at any time during the test.

This pattern has helped me a lot; I wish I had this in mind when I was writing lots of unit tests for controllers a year and a half ago (so I didn’t have to go back and change some of them later).

Another isolation/mocking framework I like to use is Moq. For the above example, there is a slight difference in the way the same fixture is used. The following is the same tests using Moq.

   1: [TestFixture]
   2: public class AdminControllerTests
   3: {
   4:     private Mock<IFacilityRepository> facilityRepository;
   5:     private Mock<IMeetingRepository> meetingRepository;
   6:     private Mock<IUserSession> userSession;
   7:  
   8:     [SetUp]
   9:     public void SetUp()
  10:     {
  11:         facilityRepository = new Mock<IFacilityRepository>();
  12:         meetingRepository = new Mock<IMeetingRepository>();
  13:         userSession = new Mock<IUserSession>();
  14:     }
  15:  
  16:     [Test]
  17:     public void SaveMeeting_should_call_Add_on_MeetingRepository_if_MeetingId_is_zero()
  18:     {
  19:         // Arrange
  20:         var meetingData = new MeetingData { MeetingId = 0, FacilityId = 0 };
  21:         meetingRepository.Setup(x => x.GetById(0)).Returns(new Meeting());
  22:         facilityRepository.Setup(x => x.GetById(0)).Returns(new Facility());
  23:         var controller = GetController();
  24:  
  25:         // Act
  26:         controller.SaveMeeting(meetingData);
  27:  
  28:         // Assert
  29:         meetingRepository.Verify(x => x.Add(It.IsAny<Meeting>()));
  30:         meetingRepository.Verify(x => x.Update(It.IsAny<Meeting>(), Times.Never()));
  31:     }
  32:  
  33:     [Test]
  34:     public void SaveMeeting_should_call_Update_on_MeetingRepository_if_MeetingId_is_not_zero()
  35:     {
  36:         // Arrange
  37:         var meetingData = new MeetingData { MeetingId = 1, FacilityId = 1 };
  38:         meetingRepository.Setup(x => x.GetById(1)).Returns(new Meeting());
  39:         facilityRepository.Setup(x => x.GetById(1)).Returns(new Facility());
  40:         var controller = GetController();
  41:  
  42:         // Act
  43:         controller.SaveMeeting(meetingData);
  44:  
  45:         // Assert
  46:         meetingRepository.Verify(x => x.Add(It.IsAny<Meeting>(), Times.Never()));
  47:         meetingRepository.Verify(x => x.Update(It.IsAny<Meeting>()));
  48:     }
  49:  
  50:     private AdminController GetController()
  51:     {
  52:         return new AdminController(userSession.Object, meetingRepository.Object, facilityRepository.Object);
  53:     }
  54: }

Both of these examples are using the Arrange/Act/Assert (AAA) syntax. The idea is that you set up your context, run some code, then verify the results of your objects and/or system under test.

This has been a very good to me and a simple pattern for testing most controllers in many of the simple MVC applications I’ve worked.

Kick It on DotNetKicks.com
Posted Feb 05 2010, 12:04 AM by Chris Missal

Comments

The Morning Brew - Chris Alcock » The Morning Brew #533 wrote The Morning Brew - Chris Alcock &raquo; The Morning Brew #533
on 02-05-2010 3:33 AM

Pingback from  The Morning Brew - Chris Alcock  » The Morning Brew #533

Sanjeev Agarwal wrote Daily tech links for .net and related technologies - Feb 5-7, 2010
on 02-05-2010 4:37 AM

Daily tech links for .net and related technologies - Feb 5-7, 2010 Web Development Using FullCalendar

Jag Reehal wrote re: Unit Testing Simple ASP.NET MVC Controllers
on 02-05-2010 5:20 AM

Hi Chris,

Yep behavior tests can be used for ASP.NET MVC Controllers and I especially like the AAA syntax :)

I did a similar post and covered other controller unit tests such as returning the correct view, and the Post-Redirect-Get pattern.

See www.arrangeactassert.com/how-to-unit-test-asp-net-mvc-controllers

Wes wrote re: Unit Testing Simple ASP.NET MVC Controllers
on 02-05-2010 4:13 PM

This type of controller approach tends to work as a service layer in an application (when concerns are properly separated).  In this case the controller is bringing together repositories and a user session which are components of the domain.

Mock based testing of service layers is often tedious and fragile as the tests duplicate the code in the service layer, any change to that code requires a change to the tests to make them pass.  I constantly ran into this problem when mocking service layers or worse when I left domain logic in service layers.

Time has shown that focusing on invariants (state based testing), is the best way, at least for me, to do testing.  In the case of a service layer, the invariants revolve around integrating components and as such testing should be from the integration perspective instead of a unit.  If I find myself excessively mocking/stubbing in a service layer, it is often a smell that I have domain logic to extract (fat service calls).  This helps maximize the testing surface of domain logic.

I usually work with a smoke test of the integration components to avoid duplicating the unit tests of the components it brings together.  This allows the underlying component architecture to change, so long as the service still fulfills the same invariant.

In this example, a good smoke test for a controller action to add an item might be to check if it exists in the repository after the call.  The repository would be stubbed instead of mocked, with an in memory version or a real database.

Another problem that frequently occurs with mock testing is the ignoring of argument values.  These are important if our tests solely rely on the interaction of components instead of the invariants.  In the example above, any Meeting object could have been added to the repository and the test would pass.

Just some thoughts on the pain points we've experienced with different testing strategies.

ASP.NET MVC Archived Blog Posts, Page 1 wrote ASP.NET MVC Archived Blog Posts, Page 1
on 02-07-2010 11:40 PM

Pingback from  ASP.NET MVC Archived Blog Posts, Page 1

Sukant Hajra wrote re: Unit Testing Simple ASP.NET MVC Controllers
on 02-10-2010 2:07 PM

the way you're doing Arrange-Act-Assert is pretty much the same as how I've been doing Given-When-Then in unit tests (without BDD-framework bloat)

Klaus Hebsgaard wrote re: Unit Testing Simple ASP.NET MVC Controllers
on 02-15-2010 2:56 PM

I have found that using the testdata builder pattern works great for controllers.

I would create the controller in a test data builder class, in a fluent like style, like:

var controller = new AdminControllerTestDataBuilder()

.WithSimpleFacilityrepository()

.WithMeetingRepository()

.withXYZ()

.Build();

And then I could insert instances where needed using :

WithFacilityrepository(facilityRepository.Object)

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright Los Techies 2008, 2009. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems