Updated: Index to all posts in this series is here!
Frankly, I’ve dreaded writing this post. Mocking is something that’s difficult to clearly get across, and the approaches and tooling for mocking vary perhaps even more widely than those for testing frameworks. There’s also an extraordinary range of terms people use to describe subtly different aspects of mocking—because we software folks don’t have enough confusing terminology in our lives already.
While I’m being specific with a mocking implementation in this post, it’s important that you keep focused at the higher level of the concepts I’m trying to get across. Look to the broad concepts and go do more exploration in the mocking tools and approaches specific to your platform.
Enough disclaimers. Let’s roll.
Why Mock?
In testing, mocking is a technique to let you cheat your system to isolate away dependencies external to the area of code you're trying to test. This is particularly important in unit testing where you want to focus on one small portion of your system—and you can’t do that if you’re having to deal with calls to external services, databases, security providers, etc.
Let’s have a look at one example. Below is some code for updating an employee’s information. This sort of action can be quite sensitive, especially since there is privacy and salary information involved. Ergo, you want to ensure there’s very solid security around this use case. My example below uses a security provider service which checks the current user and sees whether that user is permitted to do the specific actions on the target employee.
public class EmployeeUpdater
{
private readonly IUpdateEmployeePermissible _updateEmployeeSecurityProvider;
public EmployeeUpdater(
IUpdateEmployeePermissible updateEmployeeSecurityProvider)
{
_updateEmployeeSecurityProvider = updateEmployeeSecurityProvider;
}
public Employee UpdateEmployeeRate(Employee targetEmployee, float newRate)
{
Employee updatedEmployee;
if (_updateEmployeeSecurityProvider.CanUpdateEmployeeRate(targetEmployee.EmployeeId))
{
updatedEmployee = new Employee(targetEmployee.EmployeeId, targetEmployee.FirstName
targetEmployee.LastName,
targetEmployee.Address, newRate);
}
else
{
throw new SecurityException(
"Invoking user doesn't have permissions to update target target employee. "
+ targetEmployee);
}
return updatedEmployee;
}
}
I’m leaving out the implementation details for the security provider in an attempt to keep this as readable as possible. What’s important is an external (to this class) service figures out whether or not the current user can modify the target employee.
Design Impacts
Depending on your tool and platform, mocking may impact how you design your system. Note the security provider is passed in to the EmployeeUpdater’s constructor. Injecting dependencies this way, or through some form of setter, is an extremely preferred design approach for many reasons.
First, the EmployeeUpdater shouldn’t have to know how to create anything. Single Responsibility Principle says this class should know how to do its job really well, and not worry about other things. If the manner of creating a new security provider changed, then we'd have to modify the EmployeeUpdater, too. That’s nuts. Let things get built elsewhere and simply passed in to objects that need them.
Secondly, injecting the dependency to the class gives us a seam where we can substitute the real item with a fake one. We can use mocking tools, or just flat code, to create that faked out item and pass it to the portion of the system we’re trying to test.
One more consideration for mocking which can impact your design: different mocking toolsets on different platforms have limitations around what sorts of objects/classes they’re able to work with. In the .NET world, for example, many mocking tools are unable to deal with static or concrete classes. You’ll need to provide interfaces or abstract implementations for the classes you’ll want to mock out. Some tools will deal with static classes, some will deal with concretes, some won’t at all, and then some platforms lend themselves to completely different approaches.
Simple Stub Mocks
There are a number of different types of mocked objects. Mocked objects may have some behavior defined in them, or they may just be empty shells which do nothing more than satisfy your test’s need to have something in the “shape” of the real object.
In my examples below I’m using RhinoMocks for .NET. RhinoMocks uses the term “stub” to refer to a mock with no behavior, and “mock” for a faked object which has some behavior defined for it. The first test I’ll show you simply checks that an Employee object is returned with the correct updated pay rate. I don’t want to deal with the external security provider on this; the test is solely focused on whether or not the update happens properly. Ergo, I want to decouple the system and ignore the real security provider—I just want that to tell the system, “Yes, the current user is authorized.”
[Test]
public void Updated_employee_has_correct_rate()
{
int userId = 1;
int oldRate = 5;
int newRate = 10;
var stubProvider = MockRepository.GenerateStub<IUpdateEmployeePermissible>();
stubProvider.Stub(x => x.CanUpdateEmployeeRate(userId)).Return(true);
Employee current = new Employee(userId, "Jim", "Holmes", "Doghouse", oldRate);
EmployeeUpdater updater = new EmployeeUpdater(stubProvider);
Employee updated = updater.UpdateEmployeeRate(current, newRate);
Assert.AreEqual(updated.HourlyRate, newRate);
}
Next we create an Employee object as test data and an EmployeeUpdater to actually test. Note that we pass in our stubbed out provider to the constructor. The EmployeeUpdater will now use the fake provider we created earlier.
The rest of the test is straight forward: invoke the method, check the results.
Because we’re using a stub, the fake provider won’t even care if we invoke CanUpdateEmployeeRate with a bogus ID. It will simply return true regardless of when or how it’s invoked.
Mocks With Behaviors
Now for the next test. We want to ensure that our security provider’s CanUpdateEmployeeRate is always invoked—this is a critical part of our overall business rules to ensure proper security for updating employees, after all.
What would happen if someone was working on the CanUpdateEmployeeRate method and commented out the security check for some troubleshooting, then mistakenly checked that in to source? You’d completely bypass the security check in production. Ick. Like that sort of thing never happens…
Mocking frameworks generally provide you ways to deal with this sort of interaction. In RhinoMocks you can use a “mock” to handle this.
[Test]
public void Verify_security_provider_is_called()
{
int userId = 1;
int oldRate = 5;
int newRate = 10;
var mockProvider = MockRepository.GenerateMock<IUpdateEmployeePermissible>();
mockProvider.Expect(x => x.CanUpdateEmployeeRate(userId)).Return(true);
Employee current = new Employee(userId, "Jim", "Holmes", "Doghouse", oldRate);
EmployeeUpdater updater = new EmployeeUpdater(mockProvider);
Employee updated = updater.UpdateEmployeeRate(current, newRate);
mockProvider.VerifyAllExpectations();
}
Several major differences here: First, we’re generating a Mock, not a Stub in the first real statement. Secondly, we’re calling mockProvider.Expect instead of .Stub. The Expect call sets an expectation that CanUpdateEmployeeRate will be called once with the specific value set in userId. The next several lines are all the same; however, the final statement in this method isn’t the usual Assert we associate with tests – it’s a call to the mocked out security provider’s VerifyAllExpectations method.
This final statement asks the mocked object to check whether or not the system under test did the things we expected it to. In this case, we expected the system under test to invoke the CanUpdateEmployeeRate method with a value of 1.
The verification will fail if the CanUpdateEmployeeRate wasn’t invoked at all, or if it was invoked with a value other than 1.
Mocking frameworks can provide a wide range of expectation checks. You can look to ordered expectations (method A called before method D), constraints (input value between x and z), and many other validations.
Tools for Creating Mocks
You might start to see that creating a series of mocks could be quite complex. For example, if my security provider depended on something else which in turn depended on something else, then I’d have a nasty tree of objects I needed to deal with. Tools exist in every platform to help ease this. Factory Girl in Ruby is one, StructureMap and Ninject in the .NET world are others.
Look up these approaches to help you ease the burden of dealing with complex object graphs both in your testing and production worlds.
Wrapping Up
This is an extremely simplistic, high-level view of mocking. My goal was to simply show where mocks might be used, and expose you to a few of the fundamental concepts around them. Mocks are an extremely powerful tool, but it’s extremely easy to use them too much, or in too confusing a manner. It’s also easy to end up testing the mock you just created instead of the system itself!
Don’t stop with this post. Go read more around mocks and figure out how they can help you better test your systems!
3 comments:
I'll drop the 'I' when the interfaces in the .NET framework drop the 'I'
@mgroves: Yeah, because we should ALL look to Microsoft as great models for our API and system design... :p
(For those of you new here, Matt is a good pal and that above comment is dripping with sarcasm.)
I'm happy without the stupid "I" in the names, it's completely unnecessary. You don't have to be consistent with your underlying framework. HOWEVER, on Microsoft certification tests, and in sample code, it is typical for them to declare variables of various types without giving us the definition for those types. So, in that case, being able to "assume" that if it starts with a capital I followed by another capital letter, then it's an interface.
In MS tests, you might see this...
ImportantClass x; (not an interface)
INotImportant y; (interface)
I've never been happy with this and have repeatedly complained about this assumption making test questions ambiguous. I don't know if they have fixed it yet. They do the same thing with the SQL exam - all first columns in table definitions are primary keys unless you're told otherwise.
Post a Comment