jueves, marzo 04, 2010

.. she spends her last dollar for a bottle of vodka for tonight ...



Drink a Mojito and leave EasyMock. In some conferences I have been recently, I have discovered that most people in unitary tests, use EasyMock for stubbing and mocking. And I am wondering, why? Mockito offers, from my point of view, the same features that EasyMock, but better readability and simplified.

Take a look of next example:

In EasyMock:


import static org.easymock.classextension.EasyMock.*;

List mock = createNiceMock(List.class);

expect(mock.get(0)).andStubReturn("one");
expect(mock.get(1)).andStubReturn("two");
mock.clear();

replay(mock);
someCodeThatInteractsWithMock();

verify(mock);


And with Mockito the same code:


import static org.mockito.Mockito.*;

List mock = mock(List.class);

when(mock.get(0)).thenReturn("one");
when(mock.get(1)).thenReturn("two");

someCodeThatInteractsWithMock();

verify(mock).clear();


In http://mockito.googlecode.com/svn/tags/latest/javadoc/org/mockito/Mockito.html anyone can see Mockito documentation and tutorial, but what I am going to explain is how Mockito help me writing better tests.

First of all, I use Mockito for stubbing classes (Business Objects). In always use Design By Interfaces approach. This approach in conjunction with Spring Framework (IoC), makes my software so modular and reusable, and as you will see soon, testable too.

Imagine we are creating a piece of software that uploads an image in FTP server. We would have an interface called FTPService with a method called int upload(String serverDirectoryPath, String filenamePath) throws NotEnoughSpaceException.

This method would upload a file to given server directory, returning the number of written bytes, and if server is full, NotEnoughSpaceException is thrown; and its implementation called FTPServiceImpl.

Moreover we would have a business object, which will act as a controller of all procedures related with uploading a file (check if uploading file exists, establishing connection, closing connection ...). This class would have a FTPService setter for injecting the implementation (in our case FTPServiceImpl).

Ok, we have got the scenario, now we start playing with Mockito.

As you can see, first problem for testing our business object is that we require a ftp server and/or network connection, but, wait, we are writing unit tests, not integration tests, so a strategy for testing controller without a server is required. And here comes stubbing and Mockito. A stub may simulate the behavior of existing code (such as a procedure on a remote machine) or be a temporary substitute for yet-to-be-developed code. And creating a stub with mockito is as simple as:

(test class)

import static org.mockito.Mockito.*;

....

ControllerBOImpl controller = new ControllerBOImpl();

//Create Mock
FTPService mockedFtp = mock(FTPService.class);

//stubbing. When upload method with "serverPath" as first argument and "filePath" as
//second argument, 1234 integer will be returned.

when(mockedFtp.upload("serverPath", "filePath")).thenReturn(1234);

//inject mock to ControllerBO
controller.setFTPService(mockedFtp);

//Now we can start testing ControllerBOImpl as a FTP Server was present.
int writtenBytes = controller.uploadFile("serverPath", "filePath");
....


Of course more steps would be required in previous example like stubbing more methods of FTPService or writing asserts for ControllerBOImpl. But I think that would be information that distracts reader from what is really important. Stubbing with Mockito is so easy.

As you have noted, when you stub something that has attributes, you must provide hardcoded values, and that values must be used in method calling. It is not the same calls controller.uploadFile("serverPath", "filePath") than controller.uploadFile("directory",”filePath") because attribute values are different.

To avoid that, hamcrest http://code.google.com/p/hamcrest/ appear on screen.


Second reason why I use Mockito. I can also use Hamcrest. Following previous example would not use the expression:

when(mockedFtp.upload("serverPath", "filePath")).thenReturn(1234);

but:

when(mockedFtp.upload(anyString(), anyString())).thenReturn(1234);


More flexible solution, any string will act in the same way. See http://mockito.googlecode.com/svn/tags/latest/javadoc/org/mockito/Mockito.html#3 for more examples about argument matchers.

Third reason. I can mock for returning exceptions.

Previous example throws an exception when hard disk becomes full. But how can I test that!!! If I can't upload a file that its size must be 500Gb or more!!!.

No problem with Mockito, I write down another test, but changing one line:

from

when(mockedFtp.upload(anyString(), anyString())).thenReturn(1234);

to

when(mockedFtp.upload(anyString(), anyString())).thenThrow(new NotEnoughSpaceException());


Easy to test that complicated and rare situation.

Fourth Reason. Mockito allow stubbing void methods.

Stubbing voids requires different approach from when(Object) because the compiler does not like void methods inside brackets.

There are four possible solutions, but I only use two of them. doThrow(Throwable) when I want to test a void method throwing an exception, and doNothing() [default behavior], when no interaction is required.

FTPService would also have a method for connecting to server, maybe called connect()throws IOException() and would throw an exception when could not establish a connection.

Two tests are required for testing that these functionalities:

First Test:

doNothing().when(mockedFtp).connect();

And Second one:

doThrow(new IOException()).when(mockedFtp).connect();


And finally, and this reason applies only people who write tests using BDD approach. In summary, means write test with follow structure, //given //when //then comments as fundamental parts of test methods.

Remember that Mockito uses when method for “record” behavior? Well, in BDD approach, when paragraph is used for calling the business method. Note that when does not fit nicely into BDD, because given paragraph is what is called when in Mockito. For that reason Mockito provides a BDDmockito class. This class changes when method name for given method name. Thanks of that, our test is more readable and follows BDD standard.

So my test in BDD would look like:

import static org.mockito.BDDMockito.*;

…..
ControllerBOImpl controller = new ControllerBOImpl();

//Create Mock
FTPService mockedFtp = mock(FTPService.class);
//Given
given(mockedFtp.upload(anyString(), anyString())).willReturn(1234);

//When
int size = controller.uploadFile("serverPath", "filePath");

//Then
assertEquals(1234, size);



And these are the four reasons why I am using Mockito, in summary because it is simpler than EasyMock, more readable and easy to use.

1 comentarios:

aldana dijo...

I completely agree with you. I also did the switch. mockito is less "exact" and strict with expectations but in the end it is more pragmatic and leads to much better maintainable tests. See also reason to switch to mockito

Donate If You Can and Find Post Useful