Friday, January 2, 2009

AutoMocking Container with TypeMock Isolator

I have implemented an AutoMocking container for the Isolator inspired by an Introduction to the AutoMocking container in StructureMap, the fact that our test setups usually are pretty messy and Jeremy's claim that TypeMock users probably don't care about dependency injection. But why do I want to use DI (Dependency Injection) in conjunctions with TypeMock? I don't want to fight a 'religious war' based on that question, but here is what I think:

  • TypeMock without DI lacks an explicit boundary that defines what the class under tests is and which classes are mocks or stubs. This is about solid design and to isolate parts of the software in a structured manner. It’s true that you can mock everything with TypeMock but this is just about mocking and not about software design.

  • DI with another mocking framework than TypeMock is like .NET without System.Reflection. I don't like to use System.Reflection but sometimes it's just the right thing. It’s the same with TypeMock. If everything (own code, legacy-code, external libraries etc.) had a solid design you wouldn't need it but sometime it makes the life so much easier.
Example without Dependency Injection
There is the class under test called ValidationViewPresenter and it has following dependencies:



A a test without DI could look like:
[Test]
public void CanValidateCreditCardNumber()
{
// Arrange
var viewMock = Isolate.Fake.Instance<ValidationView>();
var customerServiceMock = Isolate.Fake.Instance<CustomerService>();
var creditCardServiceMock = Isolate.Fake.Instance<CreditcardService>();
var customer56 = new Customer {CreditCardNr = "5500 0001 0001 0001"};

// Mock the (hidden) dependencies
Isolate.Swap.NextInstance<ValidationView>().With(viewMock);
Isolate.Swap.NextInstance<CustomerService>().With(customerServiceMock);
Isolate.Swap.NextInstance<CreditcardService>().With(creditCardServiceMock);
// Mock the calls to the dependencies
Isolate.WhenCalled(()=>viewMock.CustomerNr).WillReturn(56);
Isolate.WhenCalled(() => customerServiceMock.GetCustomer(56)).WillReturn(customer56);
Isolate.WhenCalled(()=> creditCardServiceMock.ValidateCreditCard("5500 0001 0001 0001")).WillReturn(true);

// Act
  var presenter = new ValidationViewPresenter();

presenter.ValidateCreditCardOfCustomer();
// Assert
Isolate.Verify.WasCalledWithExactArguments(() => viewMock.IsValid = true);
}

Example with Dependency Injection
After introducing constructor injection it looks like:
[Test]
public void CanValidateCreditCardNumber()
{
// Arrange
var viewMock = Isolate.Fake.Instance<ValidationView>();
var customerServiceMock = Isolate.Fake.Instance<CustomerService>();
var creditCardServiceMock = Isolate.Fake.Instance<CreditcardService>();
var customer56 = new Customer {CreditCardNr = "5500 0001 0001 0001"};
// Inject dependencies manually
  var presenter = new ValidationViewPresenter(viewMock, customerServiceMock, creditCardServiceMock);

Isolate.WhenCalled(()=> viewMock.CustomerNr).WillReturn(56);
Isolate.WhenCalled(() => customerServiceMock.GetCustomer(56)).WillReturn(customer56);
Isolate.WhenCalled(() => creditCardServiceMock.ValidateCreditCard("5500 0001 0001 0001")).WillReturn(true);

// Act
presenter.ValidateCreditCardOfCustomer();

// Assert
Isolate.Verify.WasCalledWithExactArguments(() => viewMock.IsValid = true);
}

Example with Automocking Container
Let's eliminate the explicit constructor injection by introducing the TypeMockAutoMocker now:

[Test]
public void CanValidateCreditCardNumber()
{
// Arrange
var customer56 = new Customer {CreditCardNr = "5500 0001 0001 0001"};
  var presenterMocker = new TypeMockAutoMocker<ValidationViewPresenter>(MockMode.AAA);

var viewMock = presenterMocker.Get<IValidationView>();
var customerServiceMock = presenterMocker.Get<ICustomerService>();
var creditCardServcieMock = presenterMocker.Get<ICreditCardService>();

Isolate.WhenCalled(() => viewMock.CustomerNr).WillReturn(56);
Isolate.WhenCalled(() => customerServiceMock.GetCustomer(56)).WillReturn(customer56);
Isolate.WhenCalled(() => creditCardServcieMock.ValidateCreditCard("5500 0001 0001 0001")).WillReturn(true);

// Act
presenterMocker.ClassUnderTest.ValidateCreditCardOfCustomer();

// Assert
Isolate.Verify.WasCalledWithExactArguments(() => viewMock.IsValid = true);
}
As you can see the AutoMocking container couldn’t eliminate a lot of complexity. This is due to the fact that the test itself is complex and there are a number of non-default preconditions.

A better example with AutoMocking Container
Following code tests the same method but with different preconditions again. But this time we can rely on the default behavior that was setup by the AutoMocking container. This means that all involved and not explicitly mocked interfaces or classes do have a default behavior.

[Test]
public void CanHandleNotExistingCustomer()
{
// Arrange
  var presenterMocker = new TypeMockAutoMocker<ValidationViewPresenter>(MockMode.AAA);

var viewMock = presenterMocker.Get<IValidationView>();
Isolate.WhenCalled(() => viewMock.CustomerNr).WillReturn(56);

// Act
presenterMocker.ClassUnderTest.ValidateCreditCardOfCustomer();

// Assert
Isolate.Verify.WasCalledWithExactArguments(() => viewMock.IsValid = false);
}
Conclusion
There is a big chance that you can get lost using TypeMock and StructureMap in conjunction. The problem is that there are too many ways how they can be combined. However I hope that the TypeMockAutoMocker could help in a way that it's defining a common pattern for how to setup the tests and how to inject the mocks. Remember always:
  • Depended classes are instantiated by the AutoMocker automatically.
  • Depended interfaces and abstract classes are instantiated as mocks. These mocks do have a default behaviour where every method can be called.
  • Depended concrete classes are instantiated by calling its greediest constructor. They are not mocked.
  • To change the default behaviour you can use Inject() to register your own mock to the AutoMocker. It's how you can setup a mock manually.

Sound’s like ‘convention over configuration’. And that makes life definitely easier.


8 comments:

Derik said...

Thanks for the link back to the dimecast episode. I am glad you found it useful and helpful for creating your automocking container for Isolator

matt said...

Excellent - this is exactly what I was thinking about doing myself after the holidays - are you going to release the source in any form?

runescape power leveling said...

lol,so nice

Jörg Jenni said...

@matt
I've asked on the structuremap-users group to contribute it to the strucutremap project. Let me see if I get an answere there otherwise I will publish it somewhere else.

matt said...

Good deal - I'm subscribed to your feed now - will stay tuned for updates!

Gil Zilberfeld said...

Jorg,
Great article! Is there a link to the code we can post to?

Gil Zilberfeld,
Typemock

jacker said...
This comment has been removed by a blog administrator.
Jörg Jenni said...

@Gil
code is at http://www.codeplex.com/typemockautomocker