Simple test case using Cactus:
[...] MyServlet myServlet = new MyServlet(); [...] public void beginXXX(WebRequest theRequest) { theRequest.addParameter("param1", "value1"); theRequest.addParameter("param2", "value2"); } public void testXXX() { myServlet.init(config); myServlet.myMethod(request, response); assertEquals("some value", session.getAttribute("some_name_set_in_mymethod")); } public void endXXX(WebResponse theResponse) { String result = theResponse.getText(); assertEquals("<html><head/><body>A GET request</body></html>", result); }
Simple test case using Mock Objects:
[...] MockHttpServletRequest myMockHttpRequest = new MockHttpServletRequest(); MockHttpServletResponse myMockHttpResponse = new MockHttpServletResponse(); MockServletConfig myMockServletConfig = new MockServletConfig(); MyServlet myServlet = new MyServlet(); [...] public void testXXX() { myMockHttpRequest.setupAddParameter("param1", "value1"); myMockHttpRequest.setupAddParameter("param2", "value2"); myMockHttpRequest.setExpectedAttribute("some_name_set_in_mymethod", "some value"); myMockHttpResponse.setExpectedOutput("<html><head/><body>A GET request</body></html>"); myServlet.init(myMockServletConfig); myServlet.myMethod(myMockHttpRequest, myMockHttpResponse); myMockHttpRequest.verify(); myMockHttpResponse.verify(); }
Comparison of pros and cons of MO versus IC.
Issues | MO | IC |
---|---|---|
MO let us test methods even before the domain objects are ready, i.e. before the implementation are ready. Or before a choice of implementation has been made. Thus, for example, it is possible to write servlet code before choosing a container. This is in accordance with XP that says: "not commit to infrastructure choice before you have to" and "write unit test first". | + | |
MO is comprehensive/universal. It adapts to all kind of unit testing: Servlet unit testing, JDBC unit testing, Struts unit testing, ... Cactus only addresses server-side testing, meaning that if in your Servlet code you have JDBC connections and you want to unit test in isolation the methods that does database access you still need to have a MO-like strategy, thus you need to understand and learn 2 strategies. | + | |
Running MO tests is very fast as it does not rely on having to run a container. Thus tests can be run very often. IC testing needs to start the container, run the tests, stop the container. However, this can be alleviated by using Ant and by using a reloadable container (the majority of containers implement dynamic reloading). | + | |
Using MO force the developer to refactor his code. As an example he needs to ensure that interfaces are provided for domain objects so that a Mock implementation can be implemented. There are other more subtle refactoring involved like smart handler passing instead of more fine grained data (thus leading to better encapsulation). It follows XP refactoring rules. Note that if you need to implement tests for existing code it can easily become a nightmare ... | + | + |
Using MO, it is not sure the classes will run correctly in the chosen container. On the other hand, IC tests ensures that all code will run perfectly well in container. | + | |
MO tests tend to be very fine-grained. Thus, there is no assurance that object interactions will work properly and thus more coarse grained tests tests are a must (integration tests, functional tests). | + | |
Using generic MO libraries is against some of MO practices. For example, a good practice is to factorize domain object asserts in the mock implementation instead of in the test case (this is called Refactored Assertions). This is possible only if the Mock implementation is project specific. So, for some parts, MO does not fit that well with the idea of generic libraries. A middle ground could probably be found. | + | |
Using MO is not simple. It needs some discipline and some
experience. Same as for unit tesing using JUnit, there are some
methodologies to follow. Some are:
|
+ | |
In some cases MO mandates creating API that are no normally
needed, like having to offer a
init(MockObject) method in a Servlet in order to
initialize a mock version of an internally used domain object. Also
the code may become more complex (even if more flexible) because
of the need for testing and not because of business requirements:
for example, one might need to introduce a factory when it was not
needed simply to be able to provide MO objects from the factory.
|
+ | |
It may not be possible to create generic MO libraries that fit all the needs. For example a generic JDBC MO library may not be possible and might need database specific MO libraries. Also the cost and complexity of a generic MO library may be higher than just reimplementing from scratch just the needed mocked parts. | + | |
MO does not always work well. For example the API being mocked need to offer the correct interfaces and means to override/set internal objects. | + |
We also believe that writing correctly MO tests is a difficult process that need some training. MO are good because they let you write better code but it is more difficult to understand than Cactus tests.
Finally, we believe that MO and Cactus are complementary in 2 areas:
Thanks a lot.
Some links: