Jul 19, 2008

Advanced EasyMock techniques

EasyMock is one of many mock libraries available for Java testing. Now it archive version 2.4 and is very powerful. For those of you who are not familiar with mock concepts start with Martin Fowler article. Detailed guide about EasyMock you can read here. I'm going to show some advanced techniques of using EasyMock in your tests.

Imagine that you have following document processing code:

public class DocumentService {
private DocumentProcessor processor;

public Document getDocument(long id) {
Document doc = //code to retrive document
processor.process(doc);
return doc;
}

public void setProcessor(DocumentProcessor processor) {
this.processor = processor;
}
}

public interface DocumentProcessor {
void process(Document document);
}

public class Document {
private String text;
private long wordsCount;

public Document(String text) {
this.text = text;
}

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public long getWordsCount() {
return wordsCount;
}

public void setWordsCount(long wordsCount) {
this.wordsCount = wordsCount;
}
}

In your tests you want to check if changes made by processor really change returned document. To check this you want to set 'wordsCount' variable value to 5. You may create your own processor to do this:

public class DocumentServiceTest {
private DocumentService documentService = new DocumentService();
private DocumentProcessor processor = new DocumentProcessor() {
public void process(Document document) {
document.setWordsCount(5);
}
};

@Test
public void testGetDocument() {
documentService.setProcessor(processor);
Document document = documentService.getDocument(3);
Assert.assertEquals(5, document.getWordsCount());
}
}

But also you can use EasyMock (in my example I use Unitils to easy manage mocks):

@RunWith(UnitilsJUnit4TestClassRunner.class)
public class DocumentServiceTest {
private DocumentService documentService = new DocumentService();

@RegularMock
private DocumentProcessor processor;

@Test
public void testGetDocument() {
expectDocumentProcessed();

documentService.setProcessor(processor);
Document document = documentService.getDocument(3);
Assert.assertEquals(5, document.getWordsCount());
}

private void expectDocumentProcessed() {
processor.process(isA(Document.class));
expectLastCall().andAnswer(new IAnswer<Object>() {
public Object answer() throws Throwable {
((Document) getCurrentArguments()[0]).setWordsCount(5);
return null;
}
});
replay(processor);
}
}

Why this is more powerful solution? Because you can change behavior of the processor in runtime and reuse the same mock in different tests with different behavior. Some times your dependencies are not so simple as document processor in my example and it is not so easy to extend or implements their methods (for example interface has 10 methods and you need only one of them). There is one more side effect of this usage - you have access to the parameters passed to the methods. You can save the parameter instance to use it in asserts or for other dependencies behavior mocking.

In 2.4 version of EasyMock 'capture' option was introduced to simplify work with method parameters. Document class from previous example doesn't have custom 'equals' method, so you can't use 'eq' method of EasyMock to check parameter of 'process' method. You can implement your own argument matcher (see IArgumentMatcher in the EasyMock guide) or use Unitils reflection comparison 'refEq', but another good option for you is to use new 'capture' option:

Capture<Document> capture = new Capture<Document>();
processor.process(and(isA(Document.class), capture(capture)));
replay(processor);

Document captured = capture.getValue();

These two techniques must be in your arsenal to use full power of EasyMock library and make your tests better. Develop with pleasure!

No comments: