Auto mocking containers are designed to reduce the friction of keeping unit test beds in sync with the code being tested as systems are updated and evolve over time.
The Dependency Inversion Principle states:
As more developers follow this and the rest of Robert Martin’s SOLID principles, methods and classes become much smaller with dependent objects injected into them, typically through constructor injection. As additional dependencies are needed (or the code is refactored to improve dependency isolation), the signatures of the constructors will change. For more information on this, please read my post on Why SOLID Matters.
As constructors change, calling code (such as factories) that instantiate these evolving classes must be updated to pass in the new dependencies. If the developer has followed a generally accepted object creation pattern (such as one of the factory patterns) this should only be in one place in the line of business code.
Unit tests are another issue, and each unit test that works with the changed class must be updated to pass in the new dependency as a mock object, even though the tests don’t depend on the new dependency. In an application with a large test bed, this could be a significant amount of work. All too often, this friction results in unit tests that don’t compile simply getting commented out and never used again.
Automocking allows the developer to create an instance of a class (the system under test) without having to explicitly create each individual dependency as a unique mock. For simple classes with few dependencies, automocking provides only marginal benefit. The real benefit comes with complex classes with multiple dependencies, or classes that change over time. Tests written for the methods in these classes typically do not need all of the dependencies explicitly mocked as part of the act or assertions, but they are required for class instantiation in arrange. Automocking allows the developer to focus on the dependencies they care about for the specific test and essentially ignore the rest.
If a class has too many dependencies it should be refactored to be more concise and focused. Whether the class is left with the multiple dependencies, or gets refactored into smaller classes, automocking reduces the friction of maintaining unit tests written against an ever changing code base by dynamically adjusting the instantiation of the systems under test. The tests that requires specific mocks will still have those mocks accessible through the automocking container.
To illustrate automocking with JustMock, lets take our old friend, the SecurityHandler for an ecommerce system, shown in Listing 1. The business logic is very simple. The SecurityHandler class uses the IValidationService to validate the identity of the user and return true if the validation passes.
Listing 1public class SecurityHandler{private readonly IValidationService _validationService;public SecurityHandler(
IValidationService validationService){this._validationService = validationService;
}public bool LoginUser(string userName, string password){int userID = _validationService.ValidateUser(userName, password);
if (userID > 0)
{return true;}return false;}}
I already have some unit tests written for this class, as shown in Listing 2. These tests use the traditional mocking style, where the dependency for the system under test is explicitly mocked. There is nothing wrong with this approach, and for these tests, it works just fine.
Listing 2[Test]public void ShouldValidateUserWithProperUserNameAndPassword(){var mock = Mock.Create<IValidationService>();string userName = "Bob";string password = "password";int userID = 5;
mock.Arrange(x => x.ValidateUser(userName, password)).Returns(userID);var sut = new SecurityHandler(mock);
var result = sut.LoginUser(userName, password);Assert.IsTrue(result);}[Test]public void ShouldNotValidateUserWithImproperUserNameOrPassword(){var mock = Mock.Create<IValidationService>();string userName = "Bob";string password = "password";int userID = 0;
mock.Arrange(x => x.ValidateUser(userName, password)).Returns(userID);var sut = new SecurityHandler(mock);
var result = sut.LoginUser(userName, password);Assert.IsFalse(result);}
Security has requested that the business logic needs to be updated to start logging invalid login attempts. In Test Driven Development (TDD) style, we write our failing unit test, as shown in Listing 3.
Listing 3[Test]public void ShouldLogInvalidLoginWithExplicitMock(){string userName = "Bob";string password = "password";int userID = 0;
var mockValidationService = Mock.Create<IValidationService>();mockValidationService.Arrange(x => x.ValidateUser(userName, password)).Returns(userID);var mockLoggingService = Mock.Create<ILoggingService>();mockLoggingService.Arrange(x => x.LogInvalidLogin(userName, password)).Occurs(1);var sut = new SecurityHandler(mockValidationService, mockLoggingService);
var result = sut.LoginUser(userName, password);mockLoggingService.AssertAll();}
The test creates two explicit mocks, one for the IValidationService (as we did in the previous tests), and the second one for the ILoggingService. We need the validation to fail, so the mock is arranged to return a zero for the userid, indicating a failed login.
In order for this test to compile, we add another parameter to the constructor, changing it to look like listing 4. Unfortunately, this causes our other tests to no longer compile.
Listing 4public class SecurityHandler{private readonly IValidationService _validationService;private readonly ILoggingService _loggingService;public SecurityHandler(
IValidationService validationService,ILoggingService loggingService){this._loggingService = loggingService;
this._validationService = validationService;
}//rest ommitted for brevity
}
I could certainly update my original tests to handle the new constructor parameter in the system under test (the SecurityHandler class). But this introduces a lot of extra work that doesn’t move the project forward.
Updating the tests to use the JustMock automocking container results in the code in Listing 5. These tests don’t care about the logging service, they are only validating the business logic that utilizes the validation service, so there isn’t any benefit to creating a mock around the logging service.
There is still a need to arrange the behavior of the mock for the validation service, and the JustMock automocking container makes it very convenient. And by using the automocking container, the tests are much less brittle, and therefore more likely to continue to add benefit as the underlying code evolves.
Listing 5[Test]public void ShouldValidateUserWithProperUserNameAndPassword(){var container = new MockingContainer<SecurityHandler>();
string userName = "Bob";string password = "password";int userID = 5;
container.Arrange<IValidationService>(x => x.ValidateUser(userName, password)).Returns(userID);var result = container.Instance.LoginUser(userName, password);Assert.IsTrue(result);}[Test]public void ShouldNotValidateUserWithImproperUserNameOrPassword(){var container = new MockingContainer<SecurityHandler>();
string userName = "Bob";string password = "password";int userID = 0;
container.Arrange<IValidationService>(x => x.ValidateUser(userName, password)).Returns(userID);var result = container.Instance.LoginUser(userName, password);Assert.IsFalse(result);}
Unit tests are only effective when they are executed, and automocking helps to make sure the tests remain active. For more information on automocking, please refer to the JustMock documentation.
Happy Coding!
Technorati Tags: JustMock,Tests,Mock,MockingContainer,Telerik,JustCode,JustTrace,JustDecompile,skimedic,Dependency Inversion Principle, Unit Tests
Philip Japikse is an international speaker, a Microsoft MVP, ASPInsider, INETA Community Champion, MCSD, CSM/ CSP, and a passionate member of the developer community. Phil has been working with .Net since the first betas, developing software for over 20 years, and heavily involved in the agile community since 2005. Phil also hosts the Hallway Conversations podcast (www.hallwayconversations.com) and serves as the Lead Director for the Cincinnati .Net User’s Group (http://www.cinnug.org). You can follow Phil on twitter via www.twitter.com/skimedic, or read his personal blog at www.skimedic.com/blog.