Friday, September 25, 2009

I don't mock much anymore

I still use Rhino Mocks. Never tried any other libraries. I probably should just so I can be more worldly but it's fit my needs and I have a laundry list a mile long of ToDos.

That being said, I've done a lot of unmentored exploration into the whole unit test and TDD countryside. At the beginning of my journey I did a metric ton of mock creation. I'm speaking to the different types of test doubles. Go check out Fowler's ever-expanding documentation on the topic.

What I found was the obvious (but not at the time); mocks are part of the test assertions. Its giving me intimate details about how an object is doing its work. As such, they should only be used to verify the test at hand. If they are not truly verifying the expected behavior then it's not a mock. It's a stub, it's a fake, it's something that's allowing the process to emulate its behavior in some integrated environment.

The tests get brittle when you mock things that have no business being mocked. What I started doing was creating everything as a stub and then used the Rhino Mocks AssertWas/WasNotCalled() extension methods. Things end up being much cleaner and far less prone to unexpected test failures.

But I ramble. We're coders. Let's look at some code. Here's an example of a test I wrote using Context/Specification (a style of testing supporting BDD) and the MSpec library. Everything is a stub and I use the AssertWas/WasNotCalled() to assert that certain collaborations occurred the way I had expected them to.

Forgive the user_registration_scenario class as I use it as the base class for a number of scenarios not shown here. It's the collection of inputs/collaborators used for the test and, per Context/Specification, the Act of Arrange/Act/Assert is at a class level, not at a method level.

public class When_a_user_registers_successfully : user_registraton_scenario
Establish context = () =>
var no_errors = new List<ErrorInfo>();

.Expect(v => v.Validate(_account))

.Expect(r => r.UsernameExists(_account.Username))

Because of = () => _service.Create(_account);

It should_send_confirmation_email =()=>
_emailService.AssertWasCalled(es => es.SendConfirmationEmail(_account));

It should_save_user_to_persistent_store =()=>
_userRepository.AssertWasCalled(r => r.CreateUserAccount(_account));

public class user_registraton_scenario
protected static IEmailService _emailService;
protected static IUserRepository _userRepository;
protected static IModelValidator _modelValidator;
protected static UserAccountCreationService _service;
protected static UserAccount _account;

Establish context = () =>
_account = new UserAccount { Username = "kdog" };
_modelValidator = MockRepository.GenerateStub<IModelValidator>();
_emailService = MockRepository.GenerateStub<IEmailService>();
_userRepository = MockRepository.GenerateStub<IUserRepository>();
_service = new UserAccountCreationService(_userRepository, _emailService, _modelValidator);

I might remark that I'm quite enamored with Context/Specification. I never liked the look of my tests so much. At work, it's agonizing because we're still at a very basic level of unit testing. I'm thinking of giving a presentation on BDD using MSpec...

No comments: