| Introduction This newsletter contains news and updates about Java development tools, and in particular topics related to the Java Power Tools Bootcamp course. In this issue, we will look at mocking and stubbing, and how to use them together to write more effective and comprehensive unit tests. Mocks, Stubs, and why you probably need both Proximo: Can any of them fight? I've got a match coming up. Slave Trader: Some are good for fighting, others for dying. You need both, I think. - Gladiator, Ridley Scott Although integration and acceptance tests have their uses, it is in fact much easier to write thorough and comprehensive tests using unit tests and mocking libraries. This is especially true when you apply Test-Driven Design techniques such as TDD and BDD to help elucidate a clean, flexible and reliable design for your application. Let's see how this might work. We will be using Mockito, a new and very promising mocking framework for Java. Mock testing comes in two flavours: Stubbing and Mocking. As the guy said in the film, you need both, I think. Stubbing is in many ways more intuitive than mocking. A stub is essentially a cheap imitation of a real class, that returns predictable data under controlled circumstances. You use a stubbed-out object to return a known result or set of data, so that you can make sure your class handles this use case correctly. Let's look at a simple example. Imagine your application has a servlet that lets people register on your site. This servlet uses a service layer to do the actual registration business logic. What might our tests look like? Let's keep things simple, and assume that a user need only provide a unique username to register. Our UserManager class has a register() method that registers a user in the database. Now we already have unit tests for the UserManager class, so we don't need to test it again here - at this stage we are interested in the RegisterUser servlet class. We will be using Mockito to stub out our domain class: we just want the register() method to return a valid User object when the servlet registers a new user. We will also use the Spring mock objects library to mock out the HTTPServlet request and response objects. Let's look at the code: import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.*; import static org.mockito.BDDMockito.*; ... /** * Using Mockito to mock out the UserManager class. */ UserManager userManager = mock(UserManager.class); @Test public void registerNewUserShouldPlaceBabblerInSession() throws Exception { // // Here we create a real instance of our servlet // class. However we initialise it with a Spring // MockServletConfig object to simulate the HTTP // session. // RegisterServlet servlet = new RegisterServlet(); servlet.setUserManager(userManager); servlet.init(new MockServletConfig()); // // Now we use Mockito to set up a UserManager // stub that returns a valid User object when // the register() method is called (note the // BDD terminology) // given(userManager.register("Joe")) .willReturn(new User("Joe")); // // Now we use the Spring mock objects to prepare // a mock HTTP request and response... MockHttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = mock(HttpServletResponse.class); request.setParameter("username", "Joe"); servlet.doGet(request, response); // // Finally, we ensure that the user has been // saved in the mocked-out HTTP session // User loggedInUser = (User) request.getSession() .getAttribute("user"); assertThat(loggedInUser, notNullValue()); assertThat(loggedInUser.getName(), is("Joe")); } Now when you stub out a class or method, you need to be confident that the class you are stubbing will do the job correctly in the deployed application. This is traditionally the role of integration tests. However if you have a corresponding unit test for your stubbed-out class that covers that use case, you shouldn't need an integration test. Mocking is the other side of the coin. Mocking is used to check that your code is behaving as you think it should, given a particular use case or set of data. The set of test data is provided by your stubs. You can then use mocking to check that your code called the correct methods on your service layer. For example, in the above example, we should ensure that the servlet does indeed call the register() method with the correct parameter in this case. We could do this as follows:
@Test public void registerNewUserShouldGoToHomePage() throws Exception { // // Set up the servlet as before // RegisterServlet servlet = new RegisterServlet(); servlet.setUserManager(userManager); servlet.init(new MockServletConfig()); // // Here we use a more traditional mocking style // to mock out the userManager class // when(userManager.register("Joe")) .thenReturn(new User("Joe")); // // Now we call the servlet as before. // MockHttpServletRequest request = new MockHttpServletRequest(); HttpServletResponse response = mock(HttpServletResponse.class); request.setParameter("username", "Joe"); servlet.doGet(request, response); // // This time we want to ensure that // the register() method was correctly // invoked. // verify(userManager).register("Joe"); // // We can also verify other classes, // such as the HTTPResponse. // verify(response).sendRedirect("/home"); } So mocking and stubbing serve two different but related functions. Stubbing helps you set up a controlled test environment to exercise your classes. Mocking helps you ensure that your classes behave the way you expect for particular use cases. Mockito helps you do both with a minimum of overhead. Training and Bootcamp updates Mocking and stubbing with Mockito, and TDD practices in general, are covered in the Java Power Tools Bootcamps, and also in our new 2-day Agile Testing in Java course, for which we will be running public sessions very soon. At Wakaleo we eat our own dog-food as far as agile practices go, and after each bootcamp we study the course feedback and see how the course material can evolve for the next session. For example, the next bootcamps will contain even more material on newer tools like Sonar, Infinitest and Mockito, and we will be covering integration of issue management systems such as Trac and JIRA with Hudson and Mylyn. We will also be moving to Eclipse 3.5 for the lab exercises. Over the next few months, we continue the Australia/New Zealand tour, in Brisbane, Sydney and Wellington. The recent Canberra Java Power Tools bootcamp was a great success. Canberra developers who missed out on the bootcamp and/or who want a rapid 1-day introduction into Maven should also consider Glen Smith's Maven Quickstart course, coming up in October. And for people in France and the UK, don't miss out on the upcoming Java Power Tools Bootcamps in Paris and London, which are being run in association with Skills Matter and which are scheduled for February 2010. |