TDD in the eyes of a simpleminded: Manual Stub .vs. Mock Stub

Source Code:


RhinoMocksExample.zip (94.18 KB)


You will need to download and install Nunit 2.2.8 and RhinoMocks(for .Net 2.0) in order to run the tests.


Background:


Not as usual, we’ll start with the code and continue with it’s UnitTests.
We have a single, simple class named MailService. This class contains one method named MailText(string textToMail, params MailInfo[] mailList) that looks like this:


public class MailService
{
   private IMailValidator m_mailValidator;
   public IMailValidator MailValidator // setter, getter

   public void MailText(string textToMail, params MailInfo[] mailList)
   {
      foreach (MailInfo mail in mailList) { //I know I can collect addresses…
         ValidationResult validationResult = this.MailValidator.Validate(mail);

         if (!validationResult.IsValid)
            throw new ArgumentException(“Invalid mail: “ + validationResult.ValidationMessage, “mail”);

         // Send the email somehow…
      }
   }
}


And here is the IMailValidator, MailInfo and ValidationResult:


public class MailInfo {
   public string Address; //not best practice
   public MailInfo(string address) { this.Address = address; }
}

public class ValidationResult {
   public bool IsValid; //not best practice
   public string ValidationMessage; //not best practice

   public ValidationResult() { IsValid = true; ValidationMessage = string.Empty; }

   public ValidationResult(bool isValid, string message) {
      IsValid = isValid;
      ValidationMessage = message;
   }
}

public interface IMailValidator {
   ValidationResult Validate(MailInfo mail);
}


That is pretty straightforward I hope.


Testing inner behavior:


MailService.MailText has one dependency to IMailValidator. One way of testing our MailService.MailText method is via manual stub implementation of IMailValidator. Let’s examine the tests and then look at the manual stub I’ve created.


1). Manual stub based testing:

[TestFixture]
public class MailServiceTests
{
   [Test]
   public void MailText_OneValidEmail_MethodWorks()
   {
      MailService service = new MailService();
      MailInfo mail1 = new MailInfo(“valid@mercury.com”);

      service.MailValidator = new StubMailValidator();

      service.MailText(“Enlarge your ‘memory stick'”, mail1);
   }

   [Test]
   [ExpectedException(typeof(ArgumentException))]
   public void MailText_OneValidEmailAndOneInvalidEmail_ThrowException()
   {
      MailService service = new MailService();
      MailInfo validMail = new MailInfo(“valid@mercury.com”);
      MailInfo invalidMail = new MailInfo(“invalid.com”);

      service.MailValidator = new StubMailValidator();

      service.MailText(“Enlarge your ‘memory stick'”, validMail, invalidMail);
   }
}


And here is the simple StubMailValidator in one of it’s versions: (This stub was developed after the first test(MailText_OneValidEmail_MethodWorks) was written)


public class StubMailValidator : IMailValidator
{
   public ValidationResult Validate(MailInfo mail)
   {
      ValidationResult res = new ValidationResult(); //default: valid.
      if (mail.Address.IndexOf(“.com”) == -1) {
         res.IsValid = false;
         res.ValidationMessage = “The email address must end with .com”;
      }

      return res;
   }
}


NOTICE: Manual stubs with inner logic can be devastating !


As you may or may not notice, this stub has a bug in it. Just look at the second test on the “invalid.com” MailInfo. This is an invalid email address and yet, our stub will say that it’s valid and fail our second test. This is extremely dangerous! It is one of the things that makes people afraid from Unit Testing. When your stub need to switch his response according to a given parameter(s) based on some logic, you better switch your manual stub with dynamic\generated stub. You don’t want any test-related logic outside of your test method. Let’s look at mocked stub testing.


2). Mock stub based testing:


I’m using Oren Eini’s (aka Ayende) RhinoMocks for simulating “controlled” implementation of IMailValidator. That means that you are creating some dynamic implementation of IMailValidtor and you are telling it how to act when the tested method calls it.


Let’s look at our first test:


[Test]
public void MailText_OneValidEmail_MethodWorks()
{
   MailInfo validMail = new MailInfo(“valid@mercury.com”);
   ValidationResult validValidationResult = ValidationResult(true, string.Empty);

   // —— (1) ——-
   MockRepository mocks = new MockRepository();
   IMailValidator validator = (IMailValidator)mocks.CreateMock<IMailValidator>();
   Expect.Call(validator.Validate(validMail)).Return(validValidationResult);
   // ——————

   service.MailValidator = validator;

   mocks.ReplayAll(); // Make sure the mock object is ready to “respond”

   service.MailText(“Enlarge your ‘memory stick'”, validMail);

   mocks.VerifyAll(); // Make sure everything was called correctly.
}


(1) – This is the important stuff. I’m asking the MockRepository to create a new Mock from IMailValidtor type that our MailService expect. The line in bold tells this story: “listen dear mocked validator, if someone calls your Validate method with the parameter validMail – please send him back validValidationResult object”. If we know that our validator will be called twice – we’ll have two lines with Expect.Call as needed.


One of our main purpose in UnitTesting is to test one method (and one method only) and see if it behaves correctly if it receive a certain data. We also want to check the stomach of the method, what happen if our tested method calls another method(dependency) that return “1” – will it still behave as expected? Via Mock object I can control the dependency object’s result easily inside the test. This make it very easy to detect bugs in contrast to our manual stub that sits somewhere else, outside of our test.


Recap:


If you have a dependency to some other object and your manual stub start to contain logic, you should mock that dependency and control the returned values in the same place you write your tests via mock object.


Bonus (delegates style Interaction Based Testing):


[Test]
public void MailText_OneValidEmail_MethodWorks2()
{
   MailInfo validMail = new MailInfo(“valid@mercury.com”);
   ValidationResult validValidationResult = ValidationResult(true, string.Empty);

   RunMock<IMailValidator>(
      delegate(IMailValidator validator, MockRepository repository) {
         Expect.Call(validator.Validate(validMail)).Return(validValidationResult);

         service.MailValidator = validator;
         repository.ReplayAll();

         service.MailText(“Enlarge your ‘memory stick'”, validMail);
      }
   );
}

private void RunMock<TObjectToMock>(MockedProc<TObjectToMock> func)
{
   MockRepository mocks = new MockRepository();
   TObjectToMock obj = (TObjectToMock)mocks.CreateMock<TObjectToMock>();

   func(obj, mocks);

   mocks.VerifyAll();
}

private delegate void MockedProc<TObjectToMock>(TObjectToMock mockedObject, MockRepository repository);


Enjoy!