0 Comments

I’ve always liked the specification base class for NUnit which David Tchepak introduced in his blog post “Calculators and a tale of two TDDs - Pt 2: BDD-style”. It’s simple and easy to use. Here's the code:

[TestFixture]
   2: public abstract class ConcernFor<T> {
   3:     protected T sut;
   4:  
   5:     [SetUp]
   6:     public void SetUp() {
   7:         Context();
   8:         sut = CreateSubjectUnderTest();
   9:         Because();
  10:     }
  11:  
  12:     protected virtual void Context() {}
  13:     protected abstract T CreateSubjectUnderTest();
  14:     protected virtual void Because() {}
  15: }

When using it, your tests (specs) will end up looking like this:

public class When_current_status_is_disconnected_and_connection_happens : ConnectionStatusChangerConcern
   2: {
   3:     protected string message = ":irc.server.net PONG irc.server.net :irc.server.net";
   4:  
   5:     protected override void Context()
   6:     {
   7:         connection.SetConnectionStatusTo(ConnectionStatus.Disconnected);
   8:     }
   9:  
  10:     protected override void Because()
  11:     {
  12:         sut.Handle(new ServerMessageReceived() { Message = message });
  13:     }

I wanted to use the same base class for my ASP.NET MVC 2 controller tests, but it didn’t take long to figure that something was amiss. Given the following action:

: [HttpPost]
   2: public ViewResult Index(RegistrationData registration)
   3: {
   4:     if (ModelState.IsValid)
   5:     {
   6:         services.SendRegistration(registration);
   7:         return View("ThankYou");
   8:     }
   9:  
  10:     return View();
  11: }

And the following test:

 public class When_registration_with_invalid_data_is_received : RegistrationControllerConcern
   2: {
   3:     protected override void Because()
   4:     {
   5:         sut.Index(Examples.RegistrationEmptyEmail);
   6:     }
   7:  
   8:     [Test]
   9:     public void Should_not_send_the_registration()
  10:     {
  11:         Assert.That(service.HasBeenSent, Is.False);
  12:     }
  13: }

I expected to see green balloons. Instead, I was getting all reds. This is because my model uses data annotation to specify the validation rules. And when run inside an unit test, the validation isn’t actually happening. You can read a more thorough explanation from the following posts:

To fix this, I decided to create a ControllerConcernFor-class with the help of NUnit which forces the model’s validation before the Because-method is called. Here’s how it looks like:

 

   1: [TestFixture]
   2: public abstract class ConcernForController<T, T2>
   3:     where T : Controller
   4:     where T2 : class
   5: {
   6:     protected T sut;
   7:     protected T2 model;
   8:
   9:     [SetUp]
  10:     public void SetUp()
  11:     {
  12:         Context();
  13:         model = CreateModelForTest();
  14:         sut = CreateSubjectUnderTest();
  15:
  16:         EnforceModelValidation();
  17:         Because();
  18:     }
  19:
  20:     protected virtual void Context() { }
  21:     protected abstract T CreateSubjectUnderTest();
  22:     protected virtual void Because() { }
  23:     protected virtual T2 CreateModelForTest()
  24:     {
  25:         return null;
  26:     }
  27:
  28:     private void EnforceModelValidation()
  29:     {
  30:         if (model == null)
  31:             return;
  32:
  33:         var validationContext = new ValidationContext(model, null, null);
  34:         var validationResults = new System.Collections.Generic.List<ValidationResult>();
  35:         Validator.TryValidateObject(model, validationContext, validationResults, true);
  36:         foreach (var validationResult in validationResults)
  37:         {
  38:             sut.ModelState.AddModelError(validationResult.MemberNames.First(), validationResult.ErrorMessage);
  39:         }
  40:     }
  41: }

And here’s an example spec:

   1: public abstract class RegistrationControllerConcern : ConcernForController<RegistrationController, RegistrationData>
   2: {
   3:     protected RegistrationServiceStub service;
   4:     protected override void Context()
   5:     {
   6:         service = new RegistrationServiceStub();
   7:     }
   8:     protected override RegistrationController CreateSubjectUnderTest()
   9:     {
  10:         var controller = new RegistrationController(service);
  11:         return controller;
  12:     }
  13: }
  14: public class When_registration_with_empty_email_is_received : RegistrationControllerConcern
  15: {
  16:     protected override RegistrationData CreateModelForTest()
  17:     {
  18:         return Examples.RegistrationEmptyEmail;
  19:     }
  20:
  21:     protected override void Because()
  22:     {
  23:         sut.Index(model);
  24:     }
  25:
  26:     [Test]
  27:     public void Should_not_send_the_registration()
  28:     {
  29:         Assert.That(service.HasBeenSent, Is.False);
  30:     }
  31: }

Easy enough.

Download

You can download the ControllerConcernFor-class from GitHub.