- 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.
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);
}
ConclusionThere 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:
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
Excellent - this is exactly what I was thinking about doing myself after the holidays - are you going to release the source in any form?
lol,so nice
@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.
Good deal - I'm subscribed to your feed now - will stay tuned for updates!
Jorg,
Great article! Is there a link to the code we can post to?
Gil Zilberfeld,
Typemock
@Gil
code is at http://www.codeplex.com/typemockautomocker
Post a Comment