Creating Mocks with RMock

Let’s face it… one of the most important concepts (imho at least) in test driven development (and unit testing in particular) is the use of mock objects in your tests. Why are mock objects important? Well, when we examine what a unit test is, it’s a test of a unit of the software system under test (SUT). When we’re unit testing code, we’re only interested in the behaviour of the code under test, not the collaborators and other resources that the SUT may use.

In addition to mocking out other objects in order to isolate the SUT, it’s also a good idea to mock out external resources as well. Rather than make actual database queries, one might mock the dao to return expected (and hopefully unexpected) data in order to test how the SUT utilizes it. And certainly if the system under test is responsible for sending out 3,000 email notifications, we don’t want those emails being sent out every time we run the test (especially if we run the test after each small change)!

I thought it would be an interesting exercise to show how to mock out an object in a test the manual way, by creating an object that simply subclasses and overrides the method that does the work we want to mock and using it as an inner class in our test. So to begin, I wrote up a simple DAO that is responsible for fetching an rss feed and returning an rss Channel object that contains data about that channel. So first I started with a simple test case and wound up with this end result (after taking the many small steps of course ;) ):


public class RssDaoTest extends TestCase {
...
	public void testFetchFromUrl() throws ParserConfigurationException,
			SAXException, IOException, XPathExpressionException {

		Channel result = rssReader
				.fetch("http://blog.james-carr.org/?feed=rss2");
		assertNotNull(result);
		assertEquals("James Carr", result.getTitle());
		assertEquals("http://blog.james-carr.org", result.getLink());
		assertEquals("Rants and Musings of an Agile Developer", result
				.getDescription());
		assertEquals("Sat, 10 Jun 2006 02:26:13 +0000", result.getPubDate());
	}
}

I leave most of the implementation details for the DAO out, except for one vital piece, the way it fetches the provided feed and gets a Dom Document from it:


DocumentBuilderFactory builderFactory = DocumentBuilderFactory
		.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document doc = builder.parse(location);

Now I run my test and after some trial and error it works perfectly. But wait! Do we really want to query the web when we run the test? What if our connection is down? What if the site hosting the rss feed is gone because the author let it go stale again? If any of these cases happen, we’re out of luck; our test is correct, but now fails due to reasons beyond it’s control.

So to begin, I create a setter method to allow the user to externally set a DocumentBuilder to use, and isolate the creation of the default DocumentBuilder to a creation method, which is called from the constructor.


public class RssDao {
	private DocumentBuilder builder = null;
...
public void setBuilder(DocumentBuilder builder){
		this.builder = builder;
	}
	private void createBuilder() throws ParserConfigurationException{
		DocumentBuilderFactory builderFactory = DocumentBuilderFactory
		.newInstance();
		setBuilder(builderFactory.newDocumentBuilder());
	}
	private Document retrieveFeed(String location) throws ParserConfigurationException, SAXException, IOException {

		Document doc = builder.parse(location);
		return doc;
	}

Now it’s time to modify our test a bit to allow us to set a DocumentBuilder that will always return a Document object parsed from a local xml file in our test data directory. The simplest thing to do here is simply subclass DocumentBuilder and modify it.


public class RssDaoTest extends TestCase {
	private class MockDocBuilder extends DocumentBuilder{
		public MockDocBuilder(){

		}
		@Override
		public Document parse(InputSource arg0) throws SAXException, IOException {
			// TODO Auto-generated method stub
			return null;
		}
		public Document parse(String arg0) throws SAXException, IOException {
			DocumentBuilderFactory builderFactory = DocumentBuilderFactory
			.newInstance();
			DocumentBuilder builder = null;
			try {
				builder = builderFactory.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			return builder.parse(new File("src/test/java/data/rssFeed.xml"));
		}
		@Override
		public boolean isNamespaceAware() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean isValidating() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public void setEntityResolver(EntityResolver arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public void setErrorHandler(ErrorHandler arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public Document newDocument() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public DOMImplementation getDOMImplementation() {
			// TODO Auto-generated method stub
			return null;
		}

	}
	protected RssDao rssReader = null;
	protected DocumentBuilder mock = null;

	protected void setUp() throws Exception {
		super.setUp();
		rssReader = new RssDao();
		mock = new MockDocBuilder();
		rssReader.setBuilder(mock);

	}
...
}

We run the test and, yes, it works! That was easy, although it does feel a bit painful having that big inner class in our test.

Another way we could accomplish the same task is to use a mock object framework to do the legwork for us. There’s plenty to choose from, including EasyMock, JMock, and RMock. All of these have their strengths and weaknesses, and unfortunately for our task EasyMock and JMock won’t meet our needs. EasyMock and JMock only create mock objects from a given interface (which is actually a very good thing, more on that at another time) and not from actual objects, but RMock can. So RMock is the mock object framework I’ll use for this example since I can create a mock object from the DocumentBuilder object (which has no interface).

First, I start by modifying my test to extend RMockTestCase and create my mock DocumentBuilder object:


public class RssDaoTest extends RMockTestCase {
...
	public void testFetchFromUrl() throws ParserConfigurationException,
			SAXException, IOException, XPathExpressionException {

		DocumentBuilder mock = (DocumentBuilder) mock(DocumentBuilder.class,
				"MockDocumentBuilder");
		...
}

From there, I go ahead and specify that when the method parse is called with the argument we’re expecting, to return the Object specified (createLocalRssFeedDocument() is omitted, but all that it does is just create a Document object from a local file specified in the UnitTest). I then set it to override the default DocumentBuilder and call startVerification so that my test will fail if the test completes and the builder object never has the parse method invoked with the argument “http://blog.james-carr.org/?feed=rss2″:


		mock.parse("http://blog.james-carr.org/?feed=rss2");
		modify().returnValue(createLocalRssFeedDocument());
		rssReader.setBuilder(mock);
		startVerification();

Rest assured, everything passes. Let’s look at a comparison of the difference in test case using RMock vs. using a manual Mock:

Manual:


public class RssDaoTest extends TestCase {
	private class MockDocBuilder extends DocumentBuilder{
		public MockDocBuilder(){

		}
		@Override
		public Document parse(InputSource arg0) throws SAXException, IOException {
			// TODO Auto-generated method stub
			return null;
		}
		public Document parse(String arg0) throws SAXException, IOException {
			DocumentBuilderFactory builderFactory = DocumentBuilderFactory
			.newInstance();
			DocumentBuilder builder = null;
			try {
				builder = builderFactory.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			return builder.parse(new File("src/test/java/data/rssFeed.xml"));
		}
		@Override
		public boolean isNamespaceAware() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean isValidating() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public void setEntityResolver(EntityResolver arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public void setErrorHandler(ErrorHandler arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public Document newDocument() {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public DOMImplementation getDOMImplementation() {
			// TODO Auto-generated method stub
			return null;
		}

	}
	protected RssDao rssReader = null;
	protected DocumentBuilder mock = null;

	protected void setUp() throws Exception {
		super.setUp();
		rssReader = new RssDao();
		mock = new MockDocBuilder();
		rssReader.setBuilder(mock);

	}

	protected void tearDown() throws Exception {
		super.tearDown();
	}

	public void testSetUp(){
		assertNotNull(rssReader);
	}

	public void testFetchFromUrl() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException{
		Channel result = rssReader.fetch("http://blog.james-carr.org/?feed=rss2");
		assertNotNull(result);
		assertEquals("James Carr", result.getTitle());
		assertEquals("http://blog.james-carr.org", result.getLink());
		assertEquals("Rants and Musings of an Agile Developer", result.getDescription());
		assertEquals("Sat, 10 Jun 2006 02:26:13 +0000", result.getPubDate());
		}

}

Using RMock:


public class RssDaoTest extends RMockTestCase {
	protected RssDao rssReader = null;

	protected void setUp() throws Exception {
		super.setUp();
		rssReader = new RssDao();

	}
	private Document createLocalRssFeedDocument() throws SAXException, IOException{
		DocumentBuilderFactory builderFactory = DocumentBuilderFactory
		.newInstance();
		DocumentBuilder builder = null;
		try {
			builder = builderFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return builder.parse(new File("src/test/java/data/rssFeed.xml"));
	}
	protected void tearDown() throws Exception {
		super.tearDown();
	}

	public void testSetUp(){
		assertNotNull(rssReader);
	}

	public void testFetchFromUrl() throws ParserConfigurationException, SAXException, IOException, XPathExpressionException{
		DocumentBuilder mock = (DocumentBuilder) mock(DocumentBuilder.class, "MockDocumentBuilder");
		mock.parse("http://blog.james-carr.org/?feed=rss2"); modify().returnValue(createLocalRssFeedDocument());
		rssReader.setBuilder(mock);
		startVerification();

		Channel result = rssReader.fetch("http://blog.james-carr.org/?feed=rss2");
		assertNotNull(result);
		assertEquals("James Carr", result.getTitle());
		assertEquals("http://blog.james-carr.org", result.getLink());
		assertEquals("Rants and Musings of an Agile Developer", result.getDescription());
		assertEquals("Sat, 10 Jun 2006 02:26:13 +0000", result.getPubDate());
		}

}

Of course, this doesn’t even begin to outline all of the features that RMock has, which are worth looking into.

You can leave a response, or trackback from your own site.

Facebook comments:

One Response to “Creating Mocks with RMock”

  1. Tammo Freese says:

    You wrote that ‘EasyMock and JMock only create mock objects from a given interface [...] and not from actual objects’. This is not true.

    In fact, both EasyMock and jMock support mocking classes. See
    http://easymock.org/EasyMock2_2_ClassExtension_Documentation.html and http://www.jmock.org/cglib.html .

Leave a Reply

Subscribe to RSS Feed Follow me on Twitter!