
It is practically no secret to anyone that when testing code that uses some external components, they often use the mock-objects approach. For those who still do not know about it, briefly explain: these are objects that have the same interface as the components used, but their behavior is fully defined in the test, and their use avoids raising the full infrastructure required for the application to run . More importantly, you can easily and unconditionally check that the code called certain methods on a mock object with certain arguments.
In this article, I will conduct a comparative analysis of two common libraries for working with mocks in Java:
EasyMock and
JMock . Awareness of
JUnit's basic knowledge is enough, and after reading this article you will have a very good idea of ​​how to use both of these libraries.
Task
As an example of what needs to be tested, we will consider some application that has approximately the following structure:
1 2 3 4 5 6 7 8 9
| public class WayTooComplexClass { public WayTooComplexClass(String serverAddress) {} public boolean save(long id, String data) {} public String get(long id) {} } |
Let the implementation hidden in ellipses use any service that has a simple HTTP API (for example,
elliptics ) as storage. In order to keep this server constantly somewhere in tests, there are at least two problems:
- It is necessary that from each machine on which the test is run, there is access to this server.
- The description of the mock-server behavior is out of the code, and therefore various troubles can arise, especially if one developer updates the test in the code and on the server, and the other does not update, and the test that took place yesterday, will suddenly break
Some people give up at this moment, say that their
“code is too complicated for Unit-tests” ™ , and they are hammered into writing. Fortunately, we are not one of those, and therefore we will raise a small HTTP server directly from the test, which will respond to the necessary requests as needed. In this example, I used
jetty for such purposes. Such code will be common when using both mock libraries:
')
Mock-server for testing (you can not read)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | public class WayTooComplexClassTest { static interface RequestHandler { String handle(String target, String data); } private static class MockHttpServerHandler extends AbstractHandler { private RequestHandler handler; public void setRequestHandler(RequestHandler handler) { this.handler = handler; } @Override public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch ) throws IOException, ServletException { String req = IOUtils.toString(request.getInputStream(), "UTF-8"); String result = handler.handle(target, req); response.setStatus(HttpStatus.ORDINAL_200_OK); response.setContentType("text/plain"); final ServletOutputStream outputStream = response.getOutputStream(); try { outputStream.print(result); } finally { outputStream.close(); } } } private static final MockHttpServerHandler SERVER_HANDLER = new MockHttpServerHandler(); @BeforeClass public static void startServer() throws Exception { org.mortbay.jetty.Server server = new org.mortbay.jetty.Server(); server.setHandler(SERVER_HANDLER); server.start(); } private final WayTooComplexClass wayTooComplex = new WayTooComplexClass("http://localhost/9001"); |
Here we have three interesting points. The first one is lines 3-5, describing the
RequestHandler
interface, which receives the purpose of the request (
for example, in the address http: // habrahabr .ru / blogs / java / 136466 / the goal will be highlighted in bold / blogs / java / 136466 / ) and data sent by the user in the request body. The second, lines 7 through 32, is the
MockHttpServerHandler
class.
RequestHandler
installed to
RequestHandler
, to which all “business logic” is delegated, and the result of its work is recorded in the HTTP response. And the third, lines 36-41, is the
startServer
method, which, as you can guess from the annotations and names, is called before any tests listed in this class are started and starts the HTTP server.
The first and easiest test
Suppose that, in theory, the code hidden in the save method must pass through
{serverAddress}/upload/{id}
and transfer
data
. Check if this happens in reality.
JMock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Mockery context = new JUnit4Mockery(); @Test public void testSaveWithJMock() { final long id = 13; final String data = "nanofilters"; final RequestHandler requestHandler = context.mock(RequestHandler.class); context.checking(new Expectations() {{ one(requestHandler).handle("/upload/" + id, data); will(returnValue("Saved " + id)); }}); SERVER_HANDLER.setRequestHandler(requestHandler); wayTooComplex.save(id, data); context.assertIsSatisfied(); } |
In the first line, we need to create a JMock context in which the test will run. This context is enough for the whole test, but to
do without it in any way . To avoid problems with several mocks of the same one, the context should be recreated before each test (that is, inside the method marked with the
@Before
annotation) In the eighth line we easily and naturally create a mock for our interface. Next, in lines 10-13, we describe which calls should occur. At first glance, the syntax is
not very intuitive , but eventually you get used to it. In line 11, we indicated that we expect exactly one call to the
handle
method with arguments
("/upload/" + id)
and
(data)
. In line 12, we say that the last call will return the value
("Saved " + id)
. Here, as you can guess,
there is no typical security . We can casually transfer there the value of the wrong type and find out about it only in runtime, by getting an exception. But if the return value is not important, then you can not write this at all: JMock automatically returns the default value (
0
,
false
,
null
or an empty string). Next, we tell our mock server to use the newly created mock handler, call the application under test method and check in line 19 that all the expected calls were made.
You can get rid of the latter by adding the
@RunWith(JMock.class)
annotation
@RunWith(JMock.class)
to the test class
@RunWith(JMock.class)
Easymock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | IMocksControl control = EasyMock.createControl(); @Test public void testSaveWithEasyMock() { final long id = 15; final String data = "cold fusion reactor"; final RequestHandler requestHandler = control.createMock(RequestHandler.class); expect(requestHandler.handle("/upload/" + id, data)).andReturn("Saved " + id); control.replay(); SERVER_HANDLER.setRequestHandler(requestHandler); wayTooComplex.save(id, data); control.verify(); } |
In the first line, we create control, which is an analogue of the JMock context. Further in the eighth line we create a mock object, in the tenth we indicate that we expect a call to the handle method with certain arguments and say in this case return a certain value. There
is a typical security : an attempt to pass an argument of a type other than String to andReturn will result in a compilation error.
With my EasyMock, from my point of view, the
specification of the expected behavior is more understandable . This approach, however, has a drawback: as you can see in line 11,
it is necessary to clearly indicate that the record of the expected behavior is completed . After all the "business logic" we reach line 17 and check that all expected methods have been called. By the way, if we don’t care what the method returns, then
for void-methods we can omit the expect
construct and simply make the call:
requestHandler.handle("/upload/" + id, data)
. In addition,
it is not necessary to use control
, and you can simply do this:
1 2 3 4 5 | final RequestHandler requestHandler = EasyMock.createMock(RequestHandler.class); |
More complex response to method calls
Suppose now that we need to test that our application behaves correctly and when an external component fails. To do this, it is enough to organize an exception in the
handle
method, and then jetty will set http status 500 itself.
JMock
1 2 3 4 5 6 7 8 | @Test public void testErrorHandlingWithJMock() { |
Nothing complicated. I think comments are superfluous. You can only add that out of the box
will
still know how to
returnIterator
, but you can arrange something of your own by implementing the
Action
interface. True, it is
not very clear, and the documentation is not so hot .
Easymock
1 2 3 4 5 6 | @Test public void testErrorHandlingWithEasyMock() { |
Here, too, everything is simple, but, as you probably already suspect, if the method returns nothing (that is, it has the void type), you have to write differently, and the
commonality of the style is lost . It would look like this:
1 2 3 4 5 6 | @Test public void testErrorHandlingWithEasyMock() { |
Besides
andThrow
you can also use
andDelegateTo
, which, as you might guess,
delegates the method call to some other object (
there is no typical security at compile time! )
andAnswer
. For the latter, you need to implement an
IAnswer
interface, and write any code inside the
answer
method. There is a little less power than JMock, but much, much more convenience.
Matching arguments when calling a method
Now suppose that we do not know exactly which arguments should be passed to the call to the mocked method. The only thing we are sure about is that the
target
argument must contain an
id
somewhere.
JMock
1 2 3 4 5 6 7 | @Test public void testArgumentMatchingWithJMock() { |
JMock
uses hamcrest matchers and allows you to add something non-standard blackjack to write your matcher.
Easymock
1 2 3 4 5 6 | @Test public void testArgumentMatchingWithEasyMock() { |
It uses its own matcher, and therefore the
set of ready-made matcher is less . However, what is already done is done fairly decently and satisfies all basic needs, and the rest is exactly the same as in JMock, you can satisfy it by implementing the necessary matcher yourself.
Number of calls
Now let's change the conditions a bit, saying that some method should be called several times. The example has become quite far from reality, but I don’t want to invent a more vital one solely to show something simple.
JMock
1 2 3 4 5 6 7 | @Test public void testMultipleInvocationsWithJMock() { |
As the attentive reader could guess, in all the previous examples the word
one
was not just like that, but indicated how many challenges to expect. It supports all the necessary number of calls: whether it is zero or not care how many.
Easymock
1 2 3 4 5 6 | @Test public void testMultipleInvocationsWithEasyMock() { |
Here, too, everything is quite simple, but there is a small minus:
there is no possibility to say "at least n times" without restriction from above . This is slightly strange, given that the
atLeastOnce
method is. To expect at least three calls, you need to write something like this code:
1 2 3 4 5 6 7 | @Test public void testMultipleInvocationsWithEasyMock() { |
Stubs
It often happens that, in principle, it is not very important for us how and when a method will be called, but it is only interesting that it existed and returned something.
JMock
1 2 3 4 5 6 7 8 | @Test public void testStubMethodsWithJMock() { |
It is noticeable that this is done
uniformly with the determination of the number of times. Instead of
allowing
you to say
ignoring
. Also, if you wish, it is easy to make the entire Mock object one big stub:
1 2 3 4 5 6 7 | @Test public void testStubObjectsWithJMock() { |
Easymock
1 2 3 4 5 6 | @Test public void testStubMethodsWithEasyMock() { |
Slightly less uniform in style, but also quite comfortable. But you can make an object with a gag only when creating:
1 2 3 4 5 | @Test public void testStubObjectsWithEasyMock() { final RequestHandler requestHandler = createNiceMock(RequestHandler.class); |
Methods then, when called, will return the default value for their type (
0
,
false
or
null
).
null
will be returned including for String
, whereas in JMock the default value for this class is the empty string.
Checking the order of calling
Often it is important in which order the methods are called. Suppose we got paranoid and immediately after downloading the file we decided to download it back and compare it with the reference one. You can make sure that the application does it
(and then you never know, it does not cause much confidence ...) , you can:
JMock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Test public void testStrictOrderingWithJMock() { |
Here we are interested in lines 9 and 13, in which we, in fact, check the order of execution. It is important to remember that
inSequence
will only follow the order of the call of the mock object that is immediately specified. Therefore, in order to track that ten different calls will be made in a strict order, you have
to write inSequence
ten times . But
you can hang on the call several sequences .
Easymock
1 2 3 4 5 6 7 | @Test public void testStrictOrderingWithEasyMock() { |
It's all a little simpler:
you can turn on and off the test
several times for each mock separately . In addition, the
whole control
can be strict (see the first example), and then the general order will be checked for all the mocks included in it. In addition, mock or control
can be made strict immediately upon creation , by saying
createStrictMock
. Of course, this amount of green needs to be diluted with a serious minus: one mock (or control)
cannot take part in several sequences . There is even no such thing.
Conditions under which the method call is allowed
In some cases, it is necessary to simulate the state of the application, which will change when calling some methods, and be checked when calling some other (or the same). Let our application contact one of the two elliptics instances (the instances are called panola and yarbo) to download the file, and then have to download it from the same instance it downloaded. This can also be checked:
JMock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @Test public void testStatesWithJMock() { |
You need to look at lines 9, 13, 17 and 21. In each of them, we check with
when
that the application is now in the correct state, and then set a new one with
then
.
Very comfortable . Proponents of the paradigm of automaton programming, probably, now think that they have found a way to test their dreams.
EasyMock: no analog
Testing multi-threaded code
Generally speaking, testing a multi-threaded code is very, very difficult, because there are a lot of different combinations of when a particular thread performs an action. Especially if you do not declare all critical sections, but try to do with a minimum of locks. Abstracting now from our wonderful application, and look directly at how things are going with the multithreading support of different libraries.
JMock
Honestly, for me it was a terrible torment: the documentation on this issue
(as, indeed, on many others) is pretty fig, and therefore I had to do it by trial and error
(and since then I was in Amsterdam, and also at the airport , it was not so easy) . As a result, I was saved manually by the downloaded version of JMock 2.6.0-RC2 with the manually downloaded version of hamcrest-core 1.3.0RC1 and the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private Synchroniser synchroniser = new Synchroniser(); private Mockery context = new JUnit4Mockery() {{ this.setThreadingPolicy(synchroniser); }}; @Test public void testConcurrencyWithJMock() { |
Here, in the first line, we create a new synchronizer, in the third we tell the context to use it, and in the 17th we wait for all the necessary operations to be performed. The fact that the
States can do this is very cool, and in some cases eliminates the need to wait until the application finishes its work.
Easymock
There is full support for multithreading out of the box. Moreover, it
can be disabled by using
makeThreadSafe(mock, false)
while writing, and if necessary,
check that the mock was used from one thread , saying
checkIsUsedInOneThread(mock, true)
Mocking classes
Sometimes it turns out that the developer of the module that needs to be replaced with mock in the test did not bother to make the interface, and we only have a specific class. Fortunately, if this class is not final and has no final methods, then we can easily mock it.
JMock
1 2 3 4 5 6 7 8 | private Mockery context = new JUnit4Mockery() {{ this.setImposteriser(ClassImposteriser.INSTANCE); }}; @Test public void testClassMockingWithJMock() { |
All you need to do is call the
setImposteriser
method in the context. In the example, this happens in the second line.
Easymock
1 2 3 4 5 6 | import static org.easymock.classextension.EasyMock.*; @Test public void testClassMockingWithEasyMock() { |
It is enough to use methods from another class, which, however, is in another maven artifact. However, with classextension, you can easily create mocks for both classes and interfaces.
Partial mocking
Still sometimes it may be necessary to make a mock only for a part of the class methods, without touching the rest.
JMock: there is no such possibility
Easymock
1 2 3 4 5 6 7 | @Test public void testPartialMockingWithEasyMock() { |
Everything is simple and clear. You can also specify which parameters to pass to which constructor. All non-mocked methods will be delegated to this class.
Conclusion
This completes the examples and comparisons. I hope everyone who has read this far now knows how to use both libraries well, and in the future, using the acquired skills, will save themselves from many mistakes.
Perhaps the reader is waiting here for an answer from me to the question “So what is better for me to use in my project? JMock or EasyMock? The answer here is very simple and unambiguous: “It depends on the requirements of the project. Each of these libraries is a tool, and each of them needs to be able to use, and one must be chosen for a specific task. ”
That's all. Waiting for interesting questions and comments!