Stub, Mock and Proxy Testing
Jakob Jenkov |
Mock testing means unit testing with mock objects as substitutes for real objects. By real objects I mean the objects the tested unit (class) will be using in the real application. If you have a class Calculator, that needs a dao (Data Access Object) object to load the data it needs from a database, then the dao object is a "real object". In order to test the Calculator class you will have to provide it with a dao object that has a valid connection to the database. In addition you have to insert the data needed for the test into the database.
Setting up the connection and inserting the data in the database can be a lot of work. Instead you can provide the Calculator instance with a fake dao class which just returns the data you need for the test. The fake dao class will not actually read the data from the database. The fake dao class is a mock object. A replacement for a real object which makes it easier to test the Calculator class. Purist mock testers would call such a fake dao for a stub. I will get to that distinction later.
The situation of the Calculator using a dao object can be generalized to "a unit that uses a collaborator". The unit is the Calculator and the collaborator is the dao object. I'll express it like this:
unit --> collaborator
The arrow means "uses". When the collaborator is exchanged with a mock (or stub), it would be expressed like this:
unit --> mock
In a unit test situation it will look like this:
unit test --> unit --> collaborator
... or...
unit test --> unit --> mock
Stubs, Mocks, and Proxies
There are three types of fake objects you can use for testing: Stubs, Mocks and Proxies. Remember, a stub, mock, or proxy replaces a collaborator of the tested unit during unit test. The stubs and mocks follows Martin Fowlers definition of stubs and mocks.
A Stub is an object that implements an interface of a component, but instead of returning what the component would return when called, the stub can be configured to return a value that suits the test. Using stubs a unit test can test if a unit can handle various return values from its collaborator. Using a stub instead of a real collaborator in a unit test could be expressed like this:
- unit test --> stub
- unit test --> unit --> stub
- unit test asserts on results and state of unit
First the unit test creates the stub and configures its return values. Then the unit test creates the unit and sets the stub on it. Now the unit test calls the unit which in turn calls the stub. Finally the unit test makes assertions about the results of the method calls on the unit.
A Mock is like a stub, only it also has methods that make it possible determine what methods where called on the Mock. Using a mock it is thus possible to both test if the unit can handle various return values correctly, and also if the unit uses the collaborator correctly. For instance, you cannot see by the value returned from a dao object whether the data was read from the database using a Statement or a PreparedStatement. Nor can you see if the connection.close() method was called before returning the value. This is possible with mocks. In other words, mocks makes it possible to test a units complete interaction with a collaborator. Not just the collaborator methods that return values used by the unit. Using a mock in a unit test could be expressed like this:
- unit test --> mock
- unit test --> unit --> mock
- unit test asserts on result and state of unit
- unit test asserts on the methods called on mock
First the unit test creates the mock and configures its return values. Then the unit test creates the unit and sets the mock on it. Now the unit test calls the unit which in turn calls the mock. Finally the unit test makes assertions about the results of the method calls on the unit. The unit test also makes assertions on what mehods were called on the mock.
Proxies in mock testing are mock objects that delegate the method calls to real collaborator objects, but still records internally what methods were called on the proxy. Thus proxies makes it possible to do mock testing with real collaborators. Using a proxy in a unit test could be expressed like this:
- unit test --> collaborator
- unit test --> proxy
- unit test --> unit --> proxy --> collaborator
- unit test asserts on result and state of unit
- unit test asserts on methods called on proxy
First the unit test creates the collaborator. Then the unit test creates the proxy for the collaborator. Third the unit test creates the unit and sets the proxy on it. Now the unit test calls the unit which in turn calls the proxy. Finally the unit test makes assertions about the results of the method calls on the unit. The unit test also makes assertions on what mehods were called on the proxy.
Stub, Mock, and Proxy Testing with Testimonial
There are several popular mock testing API's available. Among others JMock and EasyMock. As of writing these two API's do not support proxies as described above. Note: They *use* java.lang.reflect.Proxy instances to implement their dynamic mocks. But that is not the same as the test proxies described above. These API's can only be used with stubs and mocks, not proxy for real collaborators. I'm sure they'll add it in the future.
For the code examples in this text I will be using my own testing API, Butterfly Testing Tools . I developed Butterfly Testing Tools because I needed the proxy testing feature that neither JMock nor EasyMock has. In addition the API's could be designed a bit more straigtforward and flexible, in my opinion (that, I guess, is a matter of personal style).
First lets see how to create stub for an interface:
Connection connection = (Connection) MockFactory.createProxy(Connection.class);
The connection variable is a stub of the Connection interface. I can now call methods on the connection instance just as if it was a real database connection. The methods will only return null though, since the stub is not configured to return any special value. In order to configure the stub to return the values appropriate for your test, you must obtain the Mock for the stub. Here is how that is done:
IMock mock = MockFactory.getMock(connection);
Now you have the mock associated with the stub. One of the methods the IMock interface has is
addReturnValue(Object returnValue);
Using the addReturnValue method you can add return values to the stub. You can add as many as you want. The return values will be returned from the stub in the same sequence they were added. Once a return value has been returned it is removed from the stub, just like in a queue. Note: The sequence of added return values must match the sequence of called methods on the stub! If you add a String "myReturnValue" as return value to the stub and then call connection.prepareStatement("select * from houses") which returns a PreparedStatement, you will get an exception. The String return value cannot be returned from the connection.prepareStatement("..."); You will have to make sure yourself that the return values and called methods on the stub match.
Once you have the IMock instance for a stub you can also make assumptions about what methods were called on the stub. The IMock interface has a series of assertCalled(MethodInvocation) methods for this purpose. Here is an example:
mock.assertCalled(new MethodInvocation("close"));
If the connection.close() method has not been called a java.lang.AssertionError is thrown. If you are using JUnit for your unit test JUnit will catch the AssertionError and report that the test failed. There are other assertCalled() etc. methods on the IMock interface. See the Testimonial project for more info.
The last thing I will show is how to make a proxy for a real connection and get the mock associated with the proxy:
//opens a real database connection.
Connection realConnection = getConnection(); Connection proxyConnection = MockFactory.createProxy(realConnection);
IMock mock = MockFactory.getMock(proxyConnection);
Simple, isn't it? It's just the same as when creating stubs for interfaces. You just provide the real collaborator to the MockFactory instead of an interface (class object). The proxyConnection will record all methods called on it before forwarding the method call to the realConnection instance. That way you can use the proxyConnection just as fine as a real connection, and at the same time make mock assertions about what methods were called on it.
You can even turn the proxyConnection into a stub temporarily by adding a return value to the proxy via the mock.addReturnValue(...). When the proxyConnection sees a return value it will return that instead of forwarding the call to the realConnection. Once all return values have been returned the proxyConnection will continue forwarding the method calls to the realConnection instance. That way you can switch between using the proxyConnection as a real connection or a stub. Smart, isn't?
It is not just database connections that are useful to mock. Any collaborator of a tested unit can potentially be mocked during testing. Whether to test a unit using a mock or a real collaborator depends on the situation. Proxies make it possible to do both at the same time, and even stub some method calls along the way. For more information about mock testing with Testimonial, see the Testimonial project page.
Tweet | |
Jakob Jenkov |