First of all, we will create a new servlet. To create that, we simply right click on project name -> New -> Other -> Servlet under Web. Click on the Next button to proceed.
JUnit is a Java unit testing framework that's one of the best test methods for regression testing. An open-source framework, it is used to write and run repeatable automated tests.
JUnit can be used as a test runner for any kind of test: e.g. system and integration tests; tests which are interacting with a deployed application.
You can do this using Mockito to have the mock return the correct params, verify they were indeed called (optionally specify number of times), write the 'result' and verify it's correct.
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.*;
import javax.servlet.http.*;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
public class TestMyServlet extends Mockito{
@Test
public void testServlet() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
when(request.getParameter("username")).thenReturn("me");
when(request.getParameter("password")).thenReturn("secret");
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
when(response.getWriter()).thenReturn(writer);
new MyServlet().doPost(request, response);
verify(request, atLeast(1)).getParameter("username"); // only if you want to verify username was called...
writer.flush(); // it may not have been flushed yet...
assertTrue(stringWriter.toString().contains("My expected string"));
}
}
First off, in a real application, you would never get database connection info in a servlet; you would configure it in your app server.
There are ways, however, of testing Servlets without having a container running. One is to use mock objects. Spring provides a set of very useful mocks for things like HttpServletRequest, HttpServletResponse, HttpServletSession, etc:
http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/mock/web/package-summary.html
Using these mocks, you could test things like
What happens if username is not in the request?
What happens if username is in the request?
etc
You could then do stuff like:
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class MyServletTest {
private MyServlet servlet;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@Before
public void setUp() {
servlet = new MyServlet();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
@Test
public void correctUsernameInRequest() throws ServletException, IOException {
request.addParameter("username", "scott");
request.addParameter("password", "tiger");
servlet.doPost(request, response);
assertEquals("text/html", response.getContentType());
// ... etc
}
}
I find Selenium tests more useful with integration or functional (end-to-end) testing. I am working with trying to use org.springframework.mock.web, but I am not very far along. I am attaching a sample controller with a jMock test suite.
First, the Controller:
package com.company.admin.web;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import com.company.admin.domain.PaymentDetail;
import com.company.admin.service.PaymentSearchService;
import com.company.admin.service.UserRequestAuditTrail;
import com.company.admin.web.form.SearchCriteria;
/**
* Controls the interactions regarding to the refunds.
*
* @author slgelma
*
*/
@Controller
@SessionAttributes({"user", "authorization"})
public class SearchTransactionController {
public static final String SEARCH_TRANSACTION_PAGE = "searchtransaction";
private PaymentSearchService searchService;
//private Validator searchCriteriaValidator;
private UserRequestAuditTrail notifications;
@Autowired
public void setSearchService(PaymentSearchService searchService) {
this.searchService = searchService;
}
@Autowired
public void setNotifications(UserRequestAuditTrail notifications) {
this.notifications = notifications;
}
@RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE)
public String setUpTransactionSearch(Model model) {
SearchCriteria searchCriteria = new SearchCriteria();
model.addAttribute("searchCriteria", searchCriteria);
notifications.transferTo(SEARCH_TRANSACTION_PAGE);
return SEARCH_TRANSACTION_PAGE;
}
@RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE, method=RequestMethod.POST, params="cancel")
public String cancelSearch() {
notifications.redirectTo(HomeController.HOME_PAGE);
return "redirect:/" + HomeController.HOME_PAGE;
}
@RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE, method=RequestMethod.POST, params="execute")
public String executeSearch(
@ModelAttribute("searchCriteria") @Valid SearchCriteria searchCriteria,
BindingResult result, Model model,
SessionStatus status) {
//searchCriteriaValidator.validate(criteria, result);
if (result.hasErrors()) {
notifications.transferTo(SEARCH_TRANSACTION_PAGE);
return SEARCH_TRANSACTION_PAGE;
} else {
PaymentDetail payment =
searchService.getAuthorizationFor(searchCriteria.geteWiseTransactionId());
if (payment == null) {
ObjectError error = new ObjectError(
"eWiseTransactionId", "Transaction not found");
result.addError(error);
model.addAttribute("searchCriteria", searchCriteria);
notifications.transferTo(SEARCH_TRANSACTION_PAGE);
return SEARCH_TRANSACTION_PAGE;
} else {
model.addAttribute("authorization", payment);
notifications.redirectTo(PaymentDetailController.PAYMENT_DETAIL_PAGE);
return "redirect:/" + PaymentDetailController.PAYMENT_DETAIL_PAGE;
}
}
}
}
Next, the test:
package test.unit.com.company.admin.web;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.support.SessionStatus;
import com.company.admin.domain.PaymentDetail;
import com.company.admin.service.PaymentSearchService;
import com.company.admin.service.UserRequestAuditTrail;
import com.company.admin.web.HomeController;
import com.company.admin.web.PaymentDetailController;
import com.company.admin.web.SearchTransactionController;
import com.company.admin.web.form.SearchCriteria;
/**
* Tests the behavior of the SearchTransactionController.
* @author slgelma
*
*/
@RunWith(JMock.class)
public class SearchTransactionControllerTest {
private final Mockery context = new JUnit4Mockery();
private final SearchTransactionController controller = new SearchTransactionController();
private final PaymentSearchService searchService = context.mock(PaymentSearchService.class);
private final UserRequestAuditTrail notifications = context.mock(UserRequestAuditTrail.class);
private final Model model = context.mock(Model.class);
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
controller.setSearchService(searchService);
controller.setNotifications(notifications);
}
@Test
public void setUpTheSearchForm() {
final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;
context.checking(new Expectations() {{
oneOf(model).addAttribute(
with(any(String.class)), with(any(Object.class)));
oneOf(notifications).transferTo(with(any(String.class)));
}});
String nextPage = controller.setUpTransactionSearch(model);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}
@Test
public void cancelSearchTest() {
final String target = HomeController.HOME_PAGE;
context.checking(new Expectations(){{
never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
oneOf(notifications).redirectTo(with(any(String.class)));
}});
String nextPage = controller.cancelSearch();
assertThat("Controller is not requesting the correct form",
nextPage, containsString(target));
}
@Test
public void executeSearchWithNullTransaction() {
final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;
final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId(null);
final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);
context.checking(new Expectations() {{
allowing(result).hasErrors(); will(returnValue(true));
never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
never(searchService).getAuthorizationFor(searchCriteria.geteWiseTransactionId());
oneOf(notifications).transferTo(with(any(String.class)));
}});
String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}
@Test
public void executeSearchWithEmptyTransaction() {
final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;
final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId("");
final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);
context.checking(new Expectations() {{
allowing(result).hasErrors(); will(returnValue(true));
never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
never(searchService).getAuthorizationFor(searchCriteria.geteWiseTransactionId());
oneOf(notifications).transferTo(with(any(String.class)));
}});
String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}
@Test
public void executeSearchWithTransactionNotFound() {
final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;
final String badTransactionId = "badboy";
final PaymentDetail transactionNotFound = null;
final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId(badTransactionId);
final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);
context.checking(new Expectations() {{
allowing(result).hasErrors(); will(returnValue(false));
atLeast(1).of(model).addAttribute(with(any(String.class)), with(any(Object.class)));
oneOf(searchService).getAuthorizationFor(with(any(String.class)));
will(returnValue(transactionNotFound));
oneOf(result).addError(with(any(ObjectError.class)));
oneOf(notifications).transferTo(with(any(String.class)));
}});
String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}
@Test
public void executeSearchWithTransactionFound() {
final String target = PaymentDetailController.PAYMENT_DETAIL_PAGE;
final String goodTransactionId = "100000010";
final PaymentDetail transactionFound = context.mock(PaymentDetail.class);
final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId(goodTransactionId);
final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);
context.checking(new Expectations() {{
allowing(result).hasErrors(); will(returnValue(false));
atLeast(1).of(model).addAttribute(with(any(String.class)), with(any(Object.class)));
oneOf(searchService).getAuthorizationFor(with(any(String.class)));
will(returnValue(transactionFound));
oneOf(notifications).redirectTo(with(any(String.class)));
}});
String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
nextPage, containsString(target));
}
}
I hope this might help.
Updated Feb 2018: OpenBrace Limited has closed down, and its ObMimic product is no longer supported.
Here's another alternative, using OpenBrace's ObMimic library of Servlet API test-doubles (disclosure: I'm its developer).
package com.openbrace.experiments.examplecode.stackoverflow5434419;
import static org.junit.Assert.*;
import com.openbrace.experiments.examplecode.stackoverflow5434419.YourServlet;
import com.openbrace.obmimic.mimic.servlet.ServletConfigMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletResponseMimic;
import com.openbrace.obmimic.substate.servlet.RequestParameters;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Example tests for {@link YourServlet#doPost(HttpServletRequest,
* HttpServletResponse)}.
*
* @author Mike Kaufman, OpenBrace Limited
*/
public class YourServletTest {
/** The servlet to be tested by this instance's test. */
private YourServlet servlet;
/** The "mimic" request to be used in this instance's test. */
private HttpServletRequestMimic request;
/** The "mimic" response to be used in this instance's test. */
private HttpServletResponseMimic response;
/**
* Create an initialized servlet and a request and response for this
* instance's test.
*
* @throws ServletException if the servlet's init method throws such an
* exception.
*/
@Before
public void setUp() throws ServletException {
/*
* Note that for the simple servlet and tests involved:
* - We don't need anything particular in the servlet's ServletConfig.
* - The ServletContext isn't relevant, so ObMimic can be left to use
* its default ServletContext for everything.
*/
servlet = new YourServlet();
servlet.init(new ServletConfigMimic());
request = new HttpServletRequestMimic();
response = new HttpServletResponseMimic();
}
/**
* Test the doPost method with example argument values.
*
* @throws ServletException if the servlet throws such an exception.
* @throws IOException if the servlet throws such an exception.
*/
@Test
public void testYourServletDoPostWithExampleArguments()
throws ServletException, IOException {
// Configure the request. In this case, all we need are the three
// request parameters.
RequestParameters parameters
= request.getMimicState().getRequestParameters();
parameters.set("username", "mike");
parameters.set("password", "xyz#zyx");
parameters.set("name", "Mike");
// Run the "doPost".
servlet.doPost(request, response);
// Check the response's Content-Type, Cache-Control header and
// body content.
assertEquals("text/html; charset=ISO-8859-1",
response.getMimicState().getContentType());
assertArrayEquals(new String[] { "no-cache" },
response.getMimicState().getHeaders().getValues("Cache-Control"));
assertEquals("...expected result from dataManager.register...",
response.getMimicState().getBodyContentAsString());
}
}
Notes:
Each "mimic" has a "mimicState" object for its logical state. This provides a clear distinction between the Servlet API methods and the configuration and inspection of the mimic's internal state.
You might be surprised that the check of Content-Type includes "charset=ISO-8859-1". However, for the given "doPost" code this is as per the Servlet API Javadoc, and the HttpServletResponse's own getContentType method, and the actual Content-Type header produced on e.g. Glassfish 3. You might not realise this if using normal mock objects and your own expectations of the API's behaviour. In this case it probably doesn't matter, but in more complex cases this is the sort of unanticipated API behaviour that can make a bit of a mockery of mocks!
I've used response.getMimicState().getContentType()
as the simplest way to check Content-Type and illustrate the above point, but you could indeed check for "text/html" on its own if you wanted (using response.getMimicState().getContentTypeMimeType()
). Checking the Content-Type header the same way as for the Cache-Control header also works.
For this example the response content is checked as character data (with this using the Writer's encoding). We could also check that the response's Writer was used rather than its OutputStream (using response.getMimicState().isWritingCharacterContent()
), but I've taken it that we're only concerned with the resulting output, and don't care what API calls produced it (though that could be checked too...). It's also possible to retrieve the response's body content as bytes, examine the detailed state of the Writer/OutputStream etc.
There are full details of ObMimic and a free download at the OpenBrace website. Or you can contact me if you have any questions (contact details are on the website).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With