When writing unit tests that involve interaction based testing, you most likely end up using one of the mocking frameworks out there. The mocking framework I'm using is Rhino Mocks. For an explanation of the differences between state based testing and interaction based testing, you can have a look at the excellent Mocks aren't Stubs from Martin Fowler. For a nice introduction on Rhino Mocks, have a look at this screen cast.
Anyway, when writing interaction based unit tests for the following code
public class CustomerService { private ICustomerRepository _customerRepository; private IActiveDirectoryGateway _activeDirectory; public CustomerService( ICustomerRepository customerRepository, IActiveDirectoryGateway activeDirectory) { _customerRepository = customerRepository; _activeDirectoryGateway = activeDirectory; } public void CreateCustomer(Customer customer) { Boolean succeeded = _customerRepository.Save(customer); if(succeeded) { _activeDirectory.GrantAccessTo(customer); } } }
you probably end up writing unit tests like this:
[TestFixture] public class ClassicCustomerServiceTestFixture { private MockRepository _mockRepository; private ICustomerRepository _customerRepository; private IActiveDirectoryGateway _activeDirectoryGateway; private CustomerService _customerService; [SetUp] public void SetUp() { _mockRepository = new MockRepository(); _customerRepository = _mockRepository .DynamicMock<ICustomerRepository>(); _activeDirectoryGateway = _mockRepository .DynamicMock<IActiveDirectoryGateway>(); _customerService = new CustomerService(_customerRepository, _activeDirectoryGateway); } [TearDown] public void TearDown() { _customerService = null; _activeDirectoryGateway = null; _customerRepository = null; _mockRepository = null; } [Test] public void VerifyInteractionWithCustomerRepository() { Customer customer = new Customer(); using(_mockRepository.Record()) { Expect.Call(_customerRepository.Save(customer)) .Return(false) .Message("Expected Save to be called once."); } using(_mockRepository.Playback()) { _customerService.CreateCustomer(customer); } } [Test] public void VerifyInteractionWithActiveDirectoryGateway() { Customer customer = CreateCustomer(); using(_mockRepository.Record()) { SetupResult.For(_customerRepository.Save(null)) .IgnoreArguments() .Return(true); _activeDirectoryGateway.GrantAccessTo(customer); LastCall.Message("Expected GrantAccessTo to be called once."); } using(_mockRepository.Playback()) { _customerService.CreateCustomer(customer); } } private static Customer CreateCustomer() { return new Customer(); } }
As you can see, there is a lot of code in the SetUp method that creates mock objects for the dependencies that are required by the subject under test ( = the CustomerService class). Besides the fact that you need to type it over and over again for every test fixture, the major disadvantage with this approach is that the unit tests are not self-containing. Reading and understanding the unit tests involves reading both the test case methods and the SetUp/TearDown methods.
The approach I've been using for the last couple of months is the AutoMocking container from Jacob Lewallen. Lets speak code shall we.
[TestFixture] public class CustomerServiceTestFixture : AutoMockingTestFixture<CustomerService> { [Test] public void VerifyInteractionWithCustomerRepository() { Customer customer = new Customer(); using(MockRepository.Record()) { Expect.Call( MockCustomerRepository.Save(customer)) .Return(false) .Message("Expected Save to be called once."); } using(MockRepository.Playback()) { CreateSubject().CreateCustomer(customer); } } [Test] public void VerifyInteractionWithActiveDirectoryGateway() { Customer customer = CreateCustomer(); using(MockRepository.Record()) { SetupResult.For( MockCustomerRepository.Save(null)) .IgnoreArguments() .Return(true); MockActiveDirectoryGateway. GrantAccessTo(customer); LastCall.Message("Expected GrantAccessTo to be called once."); } using(MockRepository.Playback()) { CreateSubject().CreateCustomer(customer); } } private ICustomerRepository MockCustomerRepository { get { return Mock<ICustomerRepository>(); } } private IActiveDirectoryGateway MockActiveDirectoryGateway { get { return Mock<IActiveDirectoryGateway>(); } } private static Customer CreateCustomer() { return new Customer(); } }
Notice how the SetUp/TearDown methods are completely gone. I've created this base class test fixture, called AutoMockingTestFixture that encapsulates the use of the AutoMocking container, like so:
public abstract class AutoMockingTestFixture<TSubject> { private AutoMockingContainer _autoMockingContainer; protected AutoMockingContainer AutoMockingContainer { get { return _autoMockingContainer; } } protected MockRepository MockRepository { get { return _autoMockingContainer.MockRepository; } } protected T CreateSubject() { return _autoMockingContainer.Create<TSubject>(); } protected T Mock<T>() where T : class { return _autoMockingContainer.Get<T>(); } protected T Stub<T>() where T : class { _autoMockingContainer.Mark<T>().Stubbed(); return _autoMockingContainer.Get<T>(); } protected virtual void SetUp() {} protected virtual void TearDown() {} [SetUp] public void BaseSetUp() { _autoMockingContainer = new AutoMockingContainer(new MockRepository()); _autoMockingContainer.Initialize(); SetUp(); CreateSubject(); } [TearDown] public void BaseTearDown() { TearDown(); _autoMockingContainer = null; } }
Using this base test fixture ensures that all interaction based unit tests in the derived test fixture are completely self-containing. Everything you need to know about a particular unit test is right there in the same method. Another big advantage is the fact that you can now add dependencies to the constructor of the CustomerService class without breaking any tests whatsoever.
If you're interested in using this approach, the code for the AutoMocking container can be downloaded from Ayende's Subversion repository (see rhino-testing). Under the hood, the AutoMocking container leverages Castle Windsor for doing its magic.
3 comments:
I've been IoC'ing and dependency injecting in my code and unit tests too lately. Works very well. However I'm a bit anxious to take the next step; adding the complexity of mocking / DI frameworks to automate some stuff that's not annoying me (yet) at this moment seems overkill.
I had seen the screencast too, but I find Ayende a bit hard to follow sometimes :-)
I've found these naming guidelines for unit tests also very helpful: http://weblogs.asp.net/rosherove/archive/2005/04/03/TestNamingStandards.aspx
I should blog about this stuff, but mentioning .NET is bad karma points for a Linux dude ;-)
I'm using Roy's naming guidelines as well, but I had to shorten the method names of my tests for blog formatting purposes :-).
I'm not using mocks in all my unit tests either (e.g. unit tests for my domain objects). I'm switching between state based testing and interaction based testing according to the situation I'm in and what feels like the most natural way in writing my unit tests.
However, when I'm doing interaction based testing, I found out that the AutoMocking container really helps out in writing clean and self-containing tests. It also made me see the reason behind Behaviour Driven Development. I'm looking into this right now, and I'll be posting about it very soon.
Post a Comment