I'd like for more of my colleagues to write unit tests. My motives are selfish. Unit tests help me figure out how methods of the unit are supposed to work. Unit tests are documentation too. And if a unit is testable, that makes it pretty good code.
I believe one reason that some of my fellows don't write unit tests is because they're not quite sure how the mock object thing fits in. I'll explain.
Mock objects are for writing unit tests.
Not integration tests, not system tests. But fast, code-covering, automated unit tests. You have to isolate the unit from other objects to make a unit test.
Mocks help us isolate units.
Isolating a unit from other objects means that the test will fail only if the unit fails, not because the network is down or because there's some bug in a library. Besides, how would you know if it's a bug in a library or a bug in the unit? I mean how would you prove it?
Isolating a unit during a test means the test controls the unit. The test controls inputs and helper objects. If my unit creates a new helper GregorianCalendar then it can't test December dates when it's not December. But if the test creates the calendar and passes it to the unit ... (See bonus section.)
If I use mock objects, then I don't have to setup a database with test data. If I use mock objects, then I don't have to setup Tomcat or some other container just to run tests on my unit.
Mock objects, and isolation, help me be a lazier test writer.
Test vs Production
To test a unit, we take it out of the production environment and put it in a test environment. The unit won't know the difference since it will be talking to mock objects that look just like the real implementations.
Production, test; same unit, different environment.
The unit in this picture has two helpers: a dao and a userService. Mock objects impersonate them in the test.Mock objects are look-alikes. A mock implements the same interface as the real helper object. It has the same method signatures but instead of implementation, a mock has the ability to take notes during a test and report back. Mocks are the henchmen of tests.
A stub differs from a mock object in that a stub object doesn't know how to tell the test about what happened during the test. Stubs don't collude. Not even in a good, testing kind of way.
Units and Tests and Mocks. Where do I start?
Let's back up a little. We want the unit to behave exactly the same in tests as in production. And it's important to have our unit do only what it has been created to do. "I wrote my unit to fetch reports for managers based on a set of given categories." We'll meet this unit soon.
If it needs help with something, like accessing data (which is some other object's specialty), then have the unit expect that helper to already be instantiated and ready to help. The unit calls the helper by calling a getter for the helper, and then the helper's method: getDao().getReport(cat). Some other object creates and destroys and hands out instances of objects that the unit needs. The unit is like the surgeon. Others hand her the tools, she just focuses on the work.
Let some other object do life-cycle management.
Make the unit mock-ready or helper-ready, expecting helper objects to already be there:
- unit declares members as their interfaces
private IDao dao;
instead ofprivate DaoImpl dao;
- which means Static objects and Singletons are not mock-able since they are not declared and can't be set to a different implementation
- the unit, when it's ready to work, should expect the dao variable to already have a value. The unit uses it by calling its getter: getDao();
- The life-cycle manager, which could be a framework or a test, must be able to give your unit a different implementation of the real helper by calling a setter: setDao();
The next three pictures show a mock-ready unit, a test that sets mock helpers and a main method that sets production helpers.
A mock-ready unit:
Test environment sets mock helpers:
Production environment sets production helpers:
The unit is the same whether it's in production or in test. The test injects mock objects in place of the helpers. The main method, the production environment, injects real implementations for the helpers.
Yeah-but Questions
Q: Is this dependency injection?
A: Yes. The unit's dependencies are set, or injected, by somebody outside the unit, a test or a framework.
Q: So I should never use "new" in my unit?
A: You probably don't need to mock a String object, so the unit could create new Strings. Or a new ArrayList. It's the helper objects that the unit shouldn't be creating.
Q: Who sets the real helpers, userService and dao, in production?
A: A container, like Spring Framework or JBoss, a main method or another object that does setup can inject real objects. Spring Framework can inject the unit's dependencies if the unit is mock-ready. The following snippet could be the unit declared in a Spring Framework configuration file. Spring injects the production implementations of the helper objects.
<bean id="unit" class="mocks.Unit"> <property name="dao" class="mocks.DaoImpl"/> <property name="userService" class="mocks.UserServiceImpl"/> </bean>
Q: What do you mean Static and Singleton objects keep a unit from being mock-ready?
A: If a unit makes a call that looks like: StaticUtil.methodZ(), there isn't a way to substitute a mock StaticUtil in for the real thing. There isn't a setter in the unit for a test to set it to a different implementation.
Q: Why is it such a big deal to not use a static object in the test?
A:It's important to focus on testing just the unit. If we don't isolate our unit we don't really know who we're talking to during a test. Do the static methods call other static objects? What if there's a bug somewhere in the static method or those other objects? We want to test our unit, not those other objects.
To unit test a unit that has a static object, you have to change how the unit uses it. Make an interface and a wrapper implementation for the Static object and add getters and setters to the unit. The unit uses this new interface and object instead of the static object.
- make an interface of StaticUtil
- make a wrapper implementation:
StaticUtilWrapper implements IStaticUtil { ... public String methodZ() { return StaticUtil.methodZ(); }
- unit declares the new interface:
private IStaticUtil util;
- make a getter and setter:
public IStaticUtil getUtil() { return this.util; }
public void setUtil(IStaticUtil util) { this.util = util; }
- unit uses util object:
String rtnval = getUtil().methodZ();
- test can substitute a different util object:
unit.setUtil((IStaticUtil)mockUtil.proxy());
Q: But static is good, isn't it, since it saves expensive object instantiation?
A: A static helper object comes at the cost of not being able to isolate and unit-test a unit that uses it. You can get the benefits of static-ness and singleton-ness other ways. The Spring Framework provides a singleton-like behavior for its beans, unless directed otherwise.
Let some other object do life-cycle management. Encapsulate that elsewhere. A unit should focus on its work, its reason for being, not on creating its helper objects. Removing object instantiation responsibilities makes the unit more cohesive. A cohesive unit means it's single-minded, it's focused on doing only one thing, which is a worthwhile development principle.
Bullet Points
- it's important to isolate a unit for a unit test
- isolation means not letting the unit have any conversations with objects not under the control of the test
- mock objects help isolate a unit for testing
- a test isolates a unit by substituting mock objects in place of the unit's real helper objects
- a mock helper object implements the same interface as the real helper, has no real implementation yet can "report back" to the test about what happened during the test
- the test does all the setup for the unit
- a unit that is mock-ready is easier to unit test
- a mock-ready unit's helpers are declared by their interface and set by somebody else
- a mock-ready unit calls methods on its helpers by first calling the getter and then the method
- removing responsibility for creating its own helper objects from a unit makes it more testable and more cohesive
So now how do I do this? How do I make mocks and how do I apply this to classes I already have?
Take a look at the method you want to test. What does it do? Does it call methods on any helper objects (besides java.lang objects like String and List)? Does it make "new" helpers? Make your unit mock-ready.
- Have the unit declare the helper as a class level member and declare it by its interface. If the helper doesn't have an interface then change that if you can; otherwise make an interface and an implementation that forwards calls to the helper.
- Make getters and setters for the helper member
- Remove any lines that create new helper objects
- Change any lines that call helper methods to something like:
getHelper().helperMethod();
We'll use JUnit to help us make tests. And there are tools out there that can help us make mock objects. JMock is one of them.
The tricky thing about a unit test
... is that we're testing only what the unit does. If a unit calls a method on a helper object, that's what we test. That the unit has made that call. We don't care how the helper does its work. We might care about inputs and outputs of the helper. Those are things our unit might work on.
In the getUserReports method in the above unit, the unit makes a call to the userService. We tell that helper mock to expect a particular method call. The mock will tell us if the unit made that call or not. It will also tell us if the arguments and return value were expected.
How do I use JMock?
(Note: these examples are in version 1.2 of JMock)
- Import the libraries:
import org.jmock.Mock;
import org.jmock.cglib.MockObjectTestCase; the test class extends this class - Make the Mock:
Mock mockUserService = mock(IUserService.class);
- Tell the mock what to expect during the test: "for this test the unit will make a call to 'getCurrentUser' on the UserService object and it will return a User object":
mockUserService.expects(once()).method("getCurrentUser").will(returnValue(testUser));
- Set the unit's userService:
unit.setUserService((IUserService)mockUserService.proxy());
- The mock will tell you anyway but you could ask it if the unit met the expectations:
mock.verify();
- See the unit test above, and visit jmock.com for more information about JMock
Write More Unit Tests with Mock Objects
By using mock objects, we can isolate and test (and document) the work done by our unit. By using mocks, we don't have to setup databases or containers to test our units. Mock objects can help us be lazy writing our tests, which means we'll write more of them, right?
BONUS: inject a calendar in a unit to test December in January:
unit declares calendar:
private Calendar calendar = null;
make setter:
public void setCalendar(Calendar thisday) { this.calendar = thisday; }
make getter (use the calendar if it has been set, else make a new one):
public Calendar getCalendar() { if (this.calendar != null ) { return (Calendar)this.calendar.clone(); } else { return new GregorianCalendar(); } }
unit uses calendar by calling getter:
Calendar startDate = getCalendar();
Calendar endDate = getCalendar();
test makes December date and sets unit's calendar:
Calendar testdate = new GregorianCalendar();
testdate.set(2007, 11, 31); // 12-31-2007
unit.setCalendar(testdate);
call the method under test and assert things about any return values:
Date rtnSaturdayDate = unit.methodWithDate();
assertEquals(<the following Saturday>, rtnSaturdayDate);
Resources
- Dependency Injection and Unit Testing
- Mocks Aren't Stubs
- Testing In Isolation and Inversion of Control
- http://www.junit.org/
- http://www.jmock.org/
- http://www.springframework.org
Return to Top