Servlet Unit Testing
Jakob Jenkov |
Unit testing servlets can be a bit tricky. The reason it is tricky is, that a servlet depends on servlet container and several servlet container interfaces. To properly test a servlet you would either have to run it inside a real servlet container, or create a mock servlet container which can be activated via code, during your unit tests. Either way, it's a bit of work to setup correctly.
What I usually do instead, is to "move the code out of the boundary class", as I have described in the text Design for Testability. Basically, I would try to push the main business logic in the servlet into a separate class which has no dependencies on the Servlet API's, if possible.
First, let's look at a really simple servlet:
public class MyServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String string1 = request.getParameter("string1"); String string2 = request.getParameter("string2"); String string3 = request.getParameter("string3"); String concatenated = string1 + "," + string2 + "," + string3; response.getWriter().write(concatenated); response.getWriter().flush(); } }
This servlet simply takes 3 parameters from the request, concatenates them into one string (comma separated), and writes the response back to the browser. It's not a very useful servlet, but I've kept it really simple to make it easier to understand what I am talking about.
The real business logic really only consists of the concatenation logic. The rest is what I call
"dispatch" logic, or "boundary" logic. So, I will take the concatenation logic and move into a separate
class, which can be tested independent from the MyServlet
class.
Here is the redesigned servlet. The code doesn't actually look much smaller than the previous servlet, but keep in mind that this is a very simple example. The changed code is marked in bold.
public class MyServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String string1 = request.getParameter("string1"); String string2 = request.getParameter("string2"); String string3 = request.getParameter("string3"); String concatenated = MyConcatenator.concatenate( string1, string2, string3); response.getWriter().write(concatenated); response.getWriter().flush(); } }
Notice how the concatenation logic has been moved to a class called MyConcatenator
.
The only thing left in the servlet is the boundary logic of getting the request parameters out of the
request object, and writing the concatenated string back to the response object. This boundary logic
is very simple, and your project can probably survive not having an automated unit test for that.
Here is what the MyConcatenator
class looks like. Yes, it's very simple, but again, that's
because I chose a very simple case - concatenating strings. If it had been image generation, it would
have been more complex. Here is the code:
public class MyConcatenator { public static String concatenate(String ... strings){ StringBuilder builder = new StringBuilder(); for(int i = 0; i<strings.length; i++){ if(i>0){ builder.append(","); } builder.append(strings[i]); } return builder.toString(); } }
Notice how the concatenate()
method only refers to a string array. No references to
servlet specific interfaces. That makes it very easy to write a unit test for the MyConcatenator
separately. Here is such a unit test:
import org.junit.Test; import static junit.framework.Assert.*; public class MyConcatenatorTest { @Test public void testConcatenate(){ String concatenated = MyConcatenator.concatenate( "one", "two", "three", "four"); assertEquals("one,two,three,four", concatenated); } }
Notice again how it was not necessary to reference any servlet classes or interfaces in this unit test.
Tweet | |
Jakob Jenkov |