#ModelAttribute controller spring-mvc mocking - unit-testing

I want to test a controller which is using #ModelAttribute for one of its method arguments.
public String processSaveAction(#ModelAttribute("exampleEntity") ExampleEntity exampleEntity)
#ModelAttribute method getExampleEntity is using #RequestParam:
#ModelAttribute("exampleEntity")
public ExampleEntity getExampleEntity(#RequestParam(value = "id", required = true) ExampleEntity exampleEntity) {
My controller is using WebDataBinder to call a factory, which returns an object based on param "id".
#Controller
public class ExampleController(){
#Autowired private IdEditorFactory idEditorFactory;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(ExampleEntity.class, idEditorFactory.createEditor(ExampleEntity.class));
}
#ModelAttribute("exampleEntity")
public ExampleEntity getExampleEntity(#RequestParam(value = "id", required = true) ExampleEntity exampleEntity) {
//Irrelevant operations
return exampleEntity;
}
#RequestMapping(method = RequestMethod.POST, params = "action=save")
public String processSaveAction(
#RequestParam(value = "confirmed") String exampleString,
#ModelAttribute("exampleEntity") ExampleEntity exampleEntity,
BindingResult result, HttpServletRequest request)
throws IOException {
boolean success = editorProcessor.processSaveAction(exampleString,
exampleEntity, result, request);
return success ? getSuccessView(exampleEntity) : VIEW_NAME;
}
}
And my test:
#WebAppConfiguration
public class ExampleControllerTest{
#Mock private EditorProcessor editorProcessor;
#Mock private IdEditorFactory idEditorFactory;
#InjectMocks private ExampleController exampleController;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(exampleController).build();
WebDataBinder webDataBinder = new WebDataBinder(ExampleEntity.class);
webDataBinder.registerCustomEditor(ExampleEntity.class, idEditorFactory.createEditor(ExampleEntity.class));
}
#Test
public void shouldProcessSaveAction() throws Exception {
// given
BindingResult result = mock(BindingResult.class);
ExampleEntity exampleEntity = mock(ExampleEntity.class);
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
given(editorProcessor.processSaveAction("confirmed", exampleEntity, result, httpServletRequest)).willReturn(true);
// when
ResultActions perform = mockMvc.perform(post("/").sessionAttr("exampleEntity", exampleEntity)
.param("id", "123456"
.param("action","save"));
// then
perform.andDo(print())
.andExpect(status().isOk());
}
}
I want to somehow mock getExampleEntity() so that every time I perform a POST with parameter "id", I receive a mocked object ("exampleEntity") for the test.
I could introduce #Binding to the test, but then I would have to mock many levels of methods (like initBinder -> idEditoryFactory-> editor -> hibernateTemplate and so on) only to get an entity from some source (for example, a database).

You can pass in the required #ModelAttribute object with the .flashAttr() method like so:
mockMvc.perform(post("/")
.param("id", "123456")
.param("action","save")
.flashAttr("exampleEntity", new ExampleEntity()));

First, test code shouldn't change our development code. #ModelAttribute will be mount from your param attribute, so .param() is enough. Below is my demo:
#Test
public void registerUser() throws Exception {
System.out.println("hello......." + rob.toString());
RequestBuilder request = post("/register.html")
.param("username", rob.getUsername())
.param("password", rob.getPassword())
.param("firstName", rob.getFirstName())
.param("lastName", rob.getLastName())
.param("email", rob.getEmail())
.with(csrf());
mvc
.perform(request)
.andDo(MockMvcResultHandlers.print())
.andExpect(redirectedUrl("/"));
}
Then is my #Controller:
#Controller
public class LoginController {
#Autowired
private UserService userService;
#RequestMapping(value = "/remove", method = RequestMethod.GET)
public String removeById(#RequestParam("userid") int id, RedirectAttributes attr) {
attr.addFlashAttribute("message", "remove!!!");
attr.addAttribute("mess", "remove ");
return "redirect:/userlist.html";
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String register(#ModelAttribute("user") User user, ModelMap model) {
System.out.println("register " + user.toString());
boolean result = userService.add(user);
model.addAttribute("message", "add " + (result ? "successed" : "failed") + "!!!");
return "/";
}
}
This can submit the right user object to the public String register(#ModelAttribute("user") User user, ModelMap model).

I'm new to Spring MVC, and currently writing a #Controller class but none of the methods have business logic, let alone HTML files for the views under'/static/'. First, I wanted to see how I can Unit Test every method to make sure all end points responded 200/ok before I inserted the business logic, you know Test Driven Development. Then I had difficulty when unit testing #PostMapping annotation method that had a #ModelAttribute assigned to it. After my whole search yesterday, I put together code for someone to unit test such cases involving #PostMapping and #ModelAttribute where you need to update the parameter values of your model attribute on the 'post' method. I'm more than welcome positive feedback to make my tests better, just wanted to post this in cases someone else that's also new wanted to test and make sure that the new info will be saved after the post in the #ModelAttribute without needing a html/jsp file for views for standalone unit testing, look at #Controller2 and String updateQuoteRequest() method for reference, and the last test in class QuoteRequestManagementController_UnitTests for more details.
pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-mvc-hotel-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>serving-web-content</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test Starter is Starter for testing Spring Boot applications
with libraries including JUnit, Hamcrest and Mockito. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Model Attribute Class:
package com.corplithotel.eventsapp.domain;
//Create the Model Attribute class, and its class members
public class QuoteRequest {
String customer;
String age;
String budget;
String eventType;
String foodAllergies;
//getters and setters
public String getCustomer() {
return customer;
}
public void setCustomer(String customer) {
this.customer = customer;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getBudget() {
return budget;
}
public void setBudget(String budget) {
this.budget = budget;
}
public String getEventType() {
return eventType;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
public String getFoodAllergies() {
return foodAllergies;
}
public void setFoodAllergies(String foodAllergies) {
this.foodAllergies = foodAllergies;
}
}
Main Class:
package com.corplithotel.eventsapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class CorpLitHotel {
public static void main(String[] args) {
SpringApplication.run(CorpLitHotel.class, args);
}
}
#Controller1
package com.corplithotel.eventsapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.corplithotel.eventsapp.domain.QuoteRequest;
//Step 1: * Create QuoteRequestController *
/*#Conroller annotation makes this class a controller, next we need to
* add 'handler mappings' to provide the controller some functionality.
* For Step 1, we won't add logic for #RequestMapping 'beginQuoteRequest()'
* & #Postrequest 'submitQuoteRequest()' methods, we will Mock the class
* and unit test in Step 2 for TDD examples:
*
*
*/
#Controller
public class QuoteRequestController {
/*#GetMapping annotation is a 'handler mapping' annotation.
* When a user comes to the page to fill out the Quote form, they
* first need to get the page. The return of the method will be a
* 'logical view name', which is just a string, and tends to correlate
* to some HTML, JSP or whatever file you're using for your View.
*
*/
#GetMapping("/newquote")
public String beginQuoteRequest(Model model) {
//Check Unit Test for logic
return "newQuote";
}//beginQuoteRequest()
/*#PosMapping annotation is another 'handler mapping' annotation.
* Once a user fills out the Quote form with their name and
* other event details, they may want to save or post that quote.
* We need to add a handler for the Post, and needs to be a separate
* method. Will be a separate page with a confirmation message to let
* the user know their Quote request has been received.
*/
#PostMapping("/newquote")
public String submitQuoteRequest(#ModelAttribute QuoteRequest formBean) {
//Check Unit Test for ideal logic
return "newQuoteConfirmation";
}//submitQuoteRequest()
}
Controller 1 Unit Tests:
package com.corplithotel.eventsapp.controller;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.corplithotel.eventsapp.domain.QuoteRequest;
/*Step 2 *Create Unit tests for QuoteRequestController*:
* this tests are assuming
*/
#ExtendWith(MockitoExtension.class)
#WebMvcTest( QuoteRequestController.class)
#TestInstance(Lifecycle.PER_CLASS)
public class QuoteRequestController_UnitTests {
#Mock
private WebApplicationContext wac;
#InjectMocks
private QuoteRequestController qrc;
private MockMvc qrcMockMvc;
#BeforeAll
public void setUp() {
qrcMockMvc = MockMvcBuilders.standaloneSetup(qrc).build();
}//BeforeAll
#Test
#DisplayName("testGetQuoteForm.. beginQuoteRequest().. Expected to pass..")
public void testGetQuoteForm() throws Exception {
//simulate getting a new form for the user to fill in (GET)
qrcMockMvc
.perform(get("/newquote"))
.andExpect(status().is(200))
.andReturn();
}//testGetQuoteForm()
#Test
#DisplayName("testPostQuoteForm().. submitQuoteRequest.. Expected to pass..")
public void testPostQuoteForm() throws Exception {
QuoteRequest aFormBean = new QuoteRequest();
qrcMockMvc
.perform(post("/newquote", aFormBean))
.andExpect(status().isOk())
.andReturn();
}//testGetQuoteForm()
}// QuoteRequestController_UnitTests
Result 1:
Junit Controller 1 Results
Controller 2:
package com.corplithotel.eventsapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.corplithotel.eventsapp.domain.QuoteRequest;
/*Step 3 *Creating QuoteRequestManagementController
* This is the controller that the sales team member
* uses to reply to a customer's request for an estimate.
* Sale's Team member can see all the incoming requests.
*
*Controller method's body for Step 3 will be empty, we will unit test
* every method of the Controller first in Step 4.
*/
#Controller
public class QuoteRequestManagementController {
/*
* We will be specifying, parameters, look for a parameter of
* a particular value; or looking for the absence of a parameter
*/
//Specifying: Sale's Team member can see all the incoming requests.
#GetMapping(path = "/quoteRequests")
public String listQuoteRequests() {
return "quoteRequestsList";
}//listRequests()
/*Parameter Of A Specific Value: Narrow down search for different
* types of sales reps. Look for 'eventType' = 'wedding' for sales reps that
* only deal with weddings and only see events associated
* with weddings.
*/
#GetMapping(path = "/quoteRequests", params="eventType=wedding")
public String listWeddingRequests() {
return "quoteWeddingRequestsList";
}//listWeddingRequests()
/*Parameter Of A Specific Value: Narrow down search for different types of sales
reps.
* Look for 'eventType' = 'birthday' for sales reps that
* only deal with weddings and only see events associated
* with weddings.
*/
#GetMapping(path = "/quoteRequests", params="eventType=birthday")
public String listBirthdayRequests() {
return "quoteBirthdayRequestsList";
}//listBirthdayRequests()
/*
* Look for 'eventType' parameter regardless of its value
*/
#GetMapping(path = "/quoteRequests", params="eventType")
public String listAllEventTypeRequests() {
return "quoteAllEventTypeRequestList";
}//listAllEventTypeRequests()
/*
* Absence of a parameter: Look for requests with no 'eventType' parameter
*/
#GetMapping(path = "/quoteRequests", params="!eventType")
public String listNoneEventTypeRequests() {
return "quoteNoneEventTypeRequestsList";
}//listNoneEventTypeRequests()
/*
* Specifying: Create another mapping for a sales rep to drill down
* from what I see in a list and pick one particular quote
* request. We will accomplish this by providing each
* quote request a unique quoteID using #PathVariable
*/
#GetMapping("/quoteRequests/{quoteID}")
public String viewQuoteRequest(#PathVariable int quoteID) {
//refer to quoteID in my implementation
return "quoteRequestsDetails";
}//viewQuoteRequest()
/*
*For this scenario lets say a sales rep is in a particular
* quote and maybe want to add a note, which will require them
* to save the content of the screen. This means we need a
* #PostMapping. The sales rep might want to update the customer
* name, event type, food allergy side note, etc.
*
*Once they hit 'save', all the data will come in and be accessible
* through #ModelAttribute and we can reference the Model Attribute in
* the method signature. So as we implement the logic in the controller
* we get to use a Model Bean and pull in all the updated data
* and ultimately save the data somewhere.
*/
#PostMapping ("/quoteUpdateDetails")
public String updateQuoteRequest(
#ModelAttribute("quoteRequest") QuoteRequest quoteRequest) {
//implement a save of all the form bean information
return "quoteUpdateDetails";
}//updateQuoteRequest()
}
Controller 2 Unit Test:
package com.corplithotel.eventsapp.controller;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import
org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import com.corplithotel.eventsapp.domain.QuoteRequest;
#ExtendWith(MockitoExtension.class)
#WebMvcTest( QuoteRequestManagementController.class)
#TestInstance(Lifecycle.PER_CLASS)
class QuoteRequestManagementController_UnitTests {
#Mock
private WebApplicationContext wac;
#InjectMocks
private QuoteRequestManagementController qrmc;
private MockMvc qrmcMockMvc;
#BeforeAll
public void setUp() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/");
viewResolver.setSuffix(".html");
qrmcMockMvc=
MockMvcBuilders.standaloneSetup(qrmc)
.setViewResolvers(viewResolver).build();
}//BeforeAll
#Test
#DisplayName("testListQuoteRequests().. Test should pass")
void testlistQuoteRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests"))
.andExpect(status().is(200))
.andReturn();
}//testlistRequests()
#Test
#DisplayName("testListWeddingRequests() .. Parameter Of A Specific Value Test1.. Test should pass")
void testlistWeddingRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?eventType=wedding"))
.andExpect(status().is(200))
.andReturn();
}//testlistWeddingRequests()
#Test
#DisplayName("testListBirthdayRequests() .. Parameter Of A Specific Value Test2.. Test should pass")
void testlistBirthdayRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?eventType=birthday"))
.andExpect(status().is(200))
.andReturn();
}//testlistBirthdayRequests()
#Test
#DisplayName("testListAllEventsRequests() .. Parameter with no specified value.. Test should pass")
void testlistAllEventsRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?eventType"))
.andExpect(status().is(200))
.andReturn();
}//testlistBirthdayRequests()
#Test
#DisplayName("testNoneEventTypeRequests() .. no parameter .. Test should pass")
void testNoneEventTypeRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?!eventType"))
.andExpect(status().is(200))
.andReturn();
}//testlistBirthdayRequests()
#Test
#DisplayName("testViewQuoteRequest().. by 'quoteID'.. Test should pass")
void testViewQuoteRequest() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests/{quoteID}", 4))
.andExpect(status().is(200))
.andReturn();
}//testViewQuoteRequest()
#Test
#DisplayName("test2ViewQuoteRequest().. by 'quoteID'.. Test should pass")
void tes2tViewQuoteRequest() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests/{quoteID}", 415))
.andExpect(status().is(200))
.andReturn();
}//testViewQuoteRequest()
#Test
void testupdateQuoteRequest() throws Exception {
MockHttpServletRequestBuilder updateDetails = post("/quoteUpdateDetails")
.param("customer", "Joe")
.param("age", "12")
.param("budget", "$1209")
.param("eventType", "wedding")
.param("foodAllergies", "fish")
.flashAttr("quoteRequest", new QuoteRequest());
qrmcMockMvc
.perform( updateDetails)
.andExpect(status().is(200));
}
}

Related

Apache Camel - testing log message

I'm trying to test a Camel route which uses from(x).to(y).log("SuccessKey123") and onException(HttpOperationFailedException.class).log("ErrorKey123").
How can I test that Camel logs "SuccessKey123" when the message was successfully processed or it logs "ErrorKey123" if HttpOperationFailedException is thrown?
My RouteBuilder():
#Component
public class myHttp4RouteBuilder extends SpringRouteBuilder {
public static final ID = "foo";
#Override
public void configure() throws Exception {
onException(HttpOperationFailedException.class)
.log("ErrorKey123")
.to(ERROR_QUEUE)
.handled(true);
from(AWS_SQS_ENDPOINT)
.to(REST_API_ENDPOINT)
.log("SuccessKey123");
}
}
Testclass:
public class myHttp4RouteBuilderTest {
#Produce(uri = MOCK_ROUTE_FROM)
protected ProducerTemplate template;
#EndpointInject(uri = MOCK_ROUTE_TO)
private MockEndpoint mockEndpoint;
#Autowired
private CamelContext camelContext;
#Before
public void setup() throws Exception{
RouteDefinition rd = camelContext.getRouteDefinition(myHttp4RouteBuilder.ID);
rd.adviceWith(camelContext, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith(MOCK_ROUTE_FROM);
weaveByToUri(ERROR_QUEUE)
.replace()
.to(MOCK_ROUTE_TO);
}
});
}
#Test
#DirtiesContext
public void testSuccess() throws Exception {
// throw an HttpOperationFailedException
mockEndpoint.whenAnyExchangeReceived(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
throw new HttpOperationFailedException("Exception", 400, null, null, null, null);
}
});
//
//
// How can I test here that camel logs "ErrorKey123"
//
//
template.sendBody(MOCK_ROUTE_FROM, "some content");
mockEndpoint.assertIsSatisfied();
}
}
Thank you very much!
Camel uses slf4j so you can just add some test appender on setup to the required logger and check what was logged after that (or even mock appender interface)
I got it ;-) You put me to the right way. Thanks!
This is my solution:
First: create a custom Appender
package de.example.test;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AppenderLoggingException;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.slf4j.event.LoggingEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
#Plugin(name="myAppenderForTesting", category="Core", elementType="appender", printObject=true)
public class MyAppenderForTesting extends AbstractAppender {
/** Here we collect all log messages */
public static List<LogEvent> logEvents = new ArrayList<>();
protected MyAppenderForTesting(String name, Filter filter, Layout<? extends Serializable> layout, final boolean ignoreExceptions) {
super(name, filter, layout, ignoreExceptions);
}
#PluginFactory
public static MyAppenderForTesting createAppender(
#PluginAttribute("name") String name,
#PluginElement("Layout") Layout<? extends Serializable> layout,
#PluginElement("Filter") final Filter filter,
#PluginAttribute("otherAttribute") String otherAttribute) {
return new MyAppenderForTesting(name, filter, layout, true);
}
#Override
public void append(LogEvent event) {
try {
logEvents.add(event);
} catch (Exception ex) {
if (!ignoreExceptions()) {
throw new AppenderLoggingException(ex);
}
} finally {
}
}
/**
* Clear log messages
*/
public static void clean() {
logEvents.clear();
}
}
Short explanation: with append() method we add each log event to a public static variable logEvents. Later in test we can access logEvents.
It was a little bit difficult to get this appender working with log4j. In my case I created a log4j2.xml in the test resources src\test\resources\log4j2.xml.
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration packages="de.example">
<Appenders>
<myAppenderForTesting name="myAppenderForTesting">
<PatternLayout alwaysWriteExceptions="false" pattern="%d{dd.MM.yyyy HH:mm:ss} %-5p %t [%C{1}.%M:%L] %m %ex{10000}%n" />
</myAppenderForTesting>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="myAppenderForTesting"/>
</Root>
</Loggers>
</Configuration>
In my test classes I can access directly to MyAppenderForTesting.logEvents. For example
for (LogEvent event : MyAppenderForTesting.logEvents) {
String message = event.getMessage().toString();
if (message.contains(search)) {
// do somethind
}
}
A different approach could be to use a log listener to collect the messages and assert them afterwards:
// adding log listener
Set<String> logMessages = new HashSet<>();
((SpringBootCamelContext) camelContext)
.addLogListener((Exchange exchange, CamelLogger camelLogger, String message) -> {
logMessages.add(message);
return message;
});
// others test initializations...
// asserting the expected log message
assertThat(logMessages.stream()
.filter(m -> m.contains("looking for this message")).count()).isEqualTo(1);
You can also use Camel's advice-with and then mock/replace those log endpoints with a mock etc, and then just assert that Camel routed a message to those depending on what you do.
http://camel.apache.org/advicewith.html
I agree with Claus Ibsen's answer. You could use AdviceWith and weaveByType(LogDefinition.class).selectIndex(...) to pinpoint the logging you expect.
Old thread but it has a high visibility, so I hope this answer helps someone.
e.g.
#SpringBootTest
#CamelSpringBootTest
public class MyRouteTest {
#Autowired
protected CamelContext context;
#EndpointInject("mock:successRoute")
private MockEndpoint successRouteMockEndpoint;
#EndpointInject("mock:failRoute")
private MockEndpoint failRouteMockEndpoint;
...
#Test
public void Given_SuccessfulCall_ThenLogSuccess() throws Exception {
AdviceWith.adviceWith(context, myRouteId,
a -> a.weaveByType(LogDefinition.class).selectIndex(1).replace().to(successRouteMockEndpoint));
// directives to mock a successful response
successRouteMockEndpoint.expectedMessageCount(1);
failRouteMockEndpoint.expectedMessageCount(0);
// trigger route
successRouteMockEndpoint.assertIsSatisfied();
failRouteMockEndpoint.assertIsSatisfied();
}
#Test
public void Given_UnsuccessfulCall_ThenLogFailure() throws Exception {
AdviceWith.adviceWith(context, myRouteId,
a -> a.weaveByType(LogDefinition.class).selectIndex(0).replace().to(failRouteMockEndpoint));
// directives to mock an unsuccessful response
successRouteMockEndpoint.expectedMessageCount(0);
failRouteMockEndpoint.expectedMessageCount(1);
// trigger route
successRouteMockEndpoint.assertIsSatisfied();
failRouteMockEndpoint.assertIsSatisfied();
}
}

How to use Tuckey urlrewrite in spring boot to access service using ?wsdl

My clients wanted to access wsdl using ?wsdl notation, but the springboot service i have created is working with only '.wsdl' format. I need a working example/sample how to configure Tuckey urlrewrite in the springboot application.
I have tried using below code, but the application complaints as it cant find urlrewrite.xml (which i have placed in src/main/resources folder.
Q1: How can i make my service to be accessible using url below
http://localhost:8080/ws/organisation?wsdl
I have tried using below code, but tuckey cannot find the urlrewrite.xml which is under src/java/resources.
#Bean
public FilterRegistrationBean tuckeyRegistrationBean() {
final FilterRegistrationBean registrationBean = new ilterRegistrationBean();
registrationBean.setFilter(new UrlRewriteFilter());
registrationBean.addInitParameter("confPath", "urlrewrite.xml");
return registrationBean;
}
Finally I could figure out a solution. This is now reading urlrewrite.xml from the src/main/resources folder.
No need to declare above mentioned bean definition in the question post (public FilterRegistrationBean tuckeyRegistrationBean()), as the below code declared as #Component will automatically register with context and url-rewriting is performed.
#Component
public class WsdlUrlRewriteFilter extends UrlRewriteFilter {
private static final String CONFIG_LOCATION = "classpath:/urlrewrite.xml";
#Value(CONFIG_LOCATION)
private Resource resource;
#Override
protected void loadUrlRewriter(FilterConfig filterConfig) throws ServletException {
try {
Conf conf = new Conf(filterConfig.getServletContext(), resource.getInputStream(), resource.getFilename(), "");
checkConf(conf);
} catch (IOException ex) {
throw new ServletException("Unable to load URL-rewrite configuration file from " + CONFIG_LOCATION, ex);
}
}
}
Best option is to write your own filter as follows.You can make use of HttpServletRequestWrapper to handle the ?wsdl extension and let the server handle the request.
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
#Component
public class WSDLQuestionMarkReplaceFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
//put init logs
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if ("wsdl".equalsIgnoreCase(httpRequest.getQueryString())) {
HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(httpRequest) {
#Override
public String getQueryString() {
return null;
}
#Override
public String getRequestURI() {
return super.getRequestURI() + ".wsdl";
}
};
chain.doFilter(requestWrapper, response);
} else {
chain.doFilter(request, response);
}
}
#Override
public void destroy() {
//put destroy logs
}
}
It worked for me. Hope it works for others too. SpringBoot+JAR
Please use the following dependency
<dependency>
<groupId>org.tuckey</groupId>
<artifactId>urlrewritefilter</artifactId>
<version>4.0.4</version>
</dependency>
Created urlrewrite.xml in resource folder
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite
PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
"http://www.tuckey.org/res/dtds/urlrewrite3.0.dtd">
<urlrewrite>
<rule>
<name>Domain Name Check</name>
<condition name="host" operator="notequal">www.userdomain.com</condition>
<from>^(.*)$</from>
<to type="redirect">http://www.userdomain.com$1</to>
</rule>
Added in main ApplicationRunner.java
#Bean
public FilterRegistrationBean tuckeyRegistrationBean() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CustomURLRewriter());
return registrationBean;
}
And created a CustomURLRewriter
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.tuckey.web.filters.urlrewrite.Conf;
import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter;
import org.tuckey.web.filters.urlrewrite.UrlRewriter;
import javax.servlet.*;
import java.io.InputStream;
public class CustomURLRewriter extends UrlRewriteFilter {
private UrlRewriter urlRewriter;
#Autowired
Environment env;
#Override
public void loadUrlRewriter(FilterConfig filterConfig) throws ServletException {
try {
ClassPathResource classPathResource = new ClassPathResource("urlrewrite.xml");
InputStream inputStream = classPathResource.getInputStream();
Conf conf1 = new Conf(filterConfig.getServletContext(), inputStream, "urlrewrite.xml", "");
urlRewriter = new UrlRewriter(conf1);
} catch (Exception e) {
throw new ServletException(e);
}
}
#Override
public UrlRewriter getUrlRewriter(ServletRequest request, ServletResponse response, FilterChain chain) {
return urlRewriter;
}
#Override
public void destroyUrlRewriter() {
if(urlRewriter != null)
urlRewriter.destroy();
}
}

Java EE 7 Restful Webservice POST method require ID type Autoincrement

I am using this Tutorial to Create simple Java EE 7 Webservices using Netbean 7.3.1.
It run successfully, I am testing using GET, PUT, POST, DELETE. All three are working fine except POST.
I am posting this json data with ID in it, which works fine.
{"id":"2","address1":"pe3","address2":"address line1","address3":"Address line3","city":"City Name","town":"Town Name","country":"uk","postcode":"123123"}
But because my ID is auto-increment, So when I POST following json without id, it fails with bellow error
{"address1":"pe3","address2":"address line1","address3":"Address line3","city":"City Name","town":"Town Name","country":"uk","postcode":"123123"}
ERROR is
HTTP Status 400 - Bad Request
type Status report
messageBad Request
descriptionThe request sent by the client was syntactically incorrect.
GlassFish Server Open Source Edition 4.0
Can anyone explain why I need to send ID which is auto-increment type in database while send POST? Is there any work around?
code in entity Class is
.......
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id")
private Integer id;
#Size(max = 45)
#Column(name = "address1")
private String address1;
#Size(max = 45)
#Column(name = "address2")
........
and AddressFacadeREST.java class looks like this.
package entities.service;
import entities.Address;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
#Stateless
#Path("entities.address")
public class AddressFacadeREST extends AbstractFacade<Address> {
#PersistenceContext(unitName = "CustomerDBPU")
private EntityManager em;
public AddressFacadeREST() {
super(Address.class);
}
#POST
#Override
#Consumes({"application/xml", "application/json"})
public void create(Address entity) {
super.create(entity);
}
#PUT
#Override
#Consumes({"application/xml", "application/json"})
public void edit(Address entity) {
super.edit(entity);
}
#DELETE
#Path("{id}")
public void remove(#PathParam("id") Integer id) {
super.remove(super.find(id));
}
#GET
#Path("{id}")
#Produces({"application/xml", "application/json"})
public Address find(#PathParam("id") Integer id) {
return super.find(id);
}
#GET
#Override
#Produces({"application/xml", "application/json"})
public List<Address> findAll() {
return super.findAll();
}
#GET
#Path("{from}/{to}")
#Produces({"application/xml", "application/json"})
public List<Address> findRange(#PathParam("from") Integer from, #PathParam("to") Integer to) {
return super.findRange(new int[]{from, to});
}
#GET
#Path("count")
#Produces("text/plain")
public String countREST() {
return String.valueOf(super.count());
}
#Override
protected EntityManager getEntityManager() {
return em;
}
}
The error is generated by jax-rs service not being able to convert the json provided into an instance of the entity class. Based on how NetBeans create Entity classes and service facades, your entity class has only one not-empty constructor, and it requires the id as an input parameter. Moreover, you have a #NotNull annotation in the #Id class. If so:
Create a constructor that takes all the entity properties except the #Id property. This way, the service will be able to convert the incoming json into an instance of your entity class.
Remove the #NotNull annotation in order to avoid the JavaBeans Validator to raise an error when persisting the new entity.

Issue on Validation using regex on validator class of spring

My requirement is that i want validation on my form field i m doing so using spring validator class and regex so what i m doing here, i am validating my DepartmentNamefield which is in String contains no numeric value any where. This validation i m performing using regex expression [0-9] because if it contains any numeric value then matcher.find() return true if it return true i am throwing error message .So problem i am facing is that when i am providing the string with non numeric value validation is done but if i m providing the pure string then still its throwing same message if i run the application again with providing pure String value then its working, but if again i am providing wrong entry ,validation is happening but after that if m providing the correct entry the same message throw so every time i need to run my application please resolve this issue
here is my validator class
package com.ankur.tutorial.validator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import com.nousinfo.tutorial.service.model.DepartmentBO;
public class DepartmentValidator implements Validator {
boolean found = false;
public boolean supports(Class<?> arg0) {
return DepartmentBO.class.isAssignableFrom(arg0);
}
public void validate(Object object, Errors errors) {
DepartmentBO departmentBO = (DepartmentBO) (object);
System.out.println(departmentBO.getDepartmentName());
if (departmentBO.getDepartmentName().equals("")) {
errors.rejectValue("departmentName", "department.Name");
} else {
Pattern pattern = Pattern.compile("[0-9]");
Matcher matcher = pattern.matcher(departmentBO.getDepartmentName());
while (matcher.find()) {
found = true;
}
System.out.println(found);
if (found) {
errors.rejectValue("departmentName", "department.string");
}
}
}
}
this is my controller
package com.nousinfo.tutorial.controllers;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
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.servlet.ModelAndView;
import com.nousinfo.tutorial.model.DepartmentForm;
import com.nousinfo.tutorial.service.impl.DepartmentServiceImpl;
import com.nousinfo.tutorial.service.model.DepartmentBO;
import com.nousinfo.tutorial.validator.DepartmentValidator;
#Controller
#RequestMapping("departmentController")
public class DepartmentController {
private DepartmentServiceImpl departmentServiceImpl;
private DepartmentValidator departmentValidator;
public DepartmentServiceImpl getDepartmentServiceImpl() {
return departmentServiceImpl;
}
public void setDepartmentServiceImpl(
DepartmentServiceImpl departmentServiceImpl) {
this.departmentServiceImpl = departmentServiceImpl;
}
public DepartmentValidator getDepartmentValidator() {
return departmentValidator;
}
public void setDepartmentValidator(DepartmentValidator departmentValidator) {
this.departmentValidator = departmentValidator;
}
/**
* Set to set the view
*
* #param model
* #return
* #throws Exception
*/
#RequestMapping(value = "/departmentForm", method = RequestMethod.GET)
public String view(Model model) throws Exception {
DepartmentBO departmentBO = new DepartmentBO();
model.addAttribute("departmentBO", departmentBO);
return "departmentForm";
}
/**
* Create the department
*
* #param departmentForm
* #param bindingResult
* #param model
* #return
* #throws Exception
*/
#RequestMapping(value = "/createDepartment", method = RequestMethod.POST)
public ModelAndView createEmployee(
#ModelAttribute("departmentBO") DepartmentBO departmentBO,
BindingResult bindingResult) throws Exception {
ModelAndView modelAndView = new ModelAndView();
departmentValidator.validate(departmentBO, bindingResult);
if (bindingResult.hasErrors()) {
modelAndView.setViewName("departmentForm");
return modelAndView;
}
modelAndView.addObject("departmentBO", departmentBO);
if (departmentBO.getUpdateStatus() == 'A') {
boolean flag = departmentServiceImpl.actionDecider(departmentBO);
if (flag == false)
modelAndView.setViewName("DBError");
else
modelAndView.setViewName("Success");
}
return modelAndView;
}
You have the variable found as a class property. Validators are singleton. Move the variable found inside the validate method.

How to test my servlet using JUnit

I have created a web system using Java Servlets and now want to make JUnit testing. My dataManager is just a basic piece of code that submits it to the database. How would you test a Servlet with JUnit?
My code example that allows a user to register/sign up, which is submitted from my main page via AJAX:
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
// Get parameters
String userName = request.getParameter("username");
String password = request.getParameter("password");
String name = request.getParameter("name");
try {
// Load the database driver
Class.forName("com.mysql.jdbc.Driver");
//pass reg details to datamanager
dataManager = new DataManager();
//store result as string
String result = dataManager.register(userName, password, name);
//set response to html + no cache
response.setContentType("text/html");
response.setHeader("Cache-Control", "no-cache");
//send response with register result
response.getWriter().write(result);
} catch(Exception e){
System.out.println("Exception is :" + e);
}
}
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).
EDIT: Cactus is now a dead project: http://attic.apache.org/projects/jakarta-cactus.html
You may want to look at cactus.
http://jakarta.apache.org/cactus/
Project Description
Cactus is a simple test framework for unit testing server-side java code (Servlets, EJBs, Tag Libs, Filters, ...).
The intent of Cactus is to lower the cost of writing tests for server-side code. It uses JUnit and extends it.
Cactus implements an in-container strategy, meaning that tests are executed inside the container.
Another approach would be to create an embedded server to "host" your servlet, allowing you to write calls against it with libraries meant to make calls to actual servers (the usefulness of this approach somewhat depends on how easily you can make "legitimate" programatic calls to the server - I was testing a JMS (Java Messaging Service) access point, for which clients abound).
There are a couple of different routes you can go - the usual two are tomcat and jetty.
Warning: something to be mindful of when choosing the server to embed is the version of servlet-api you are using (the library which provides classes like HttpServletRequest). If you are using 2.5, I found Jetty 6.x to work well (which is the example I'll give below). If you're using servlet-api 3.0, the tomcat-7 embedded stuff seems to be a good option, however I had to abandon my attempt to use it, as the application I was testing used servlet-api 2.5. Trying to mix the two will result in NoSuchMethod and other such exceptions when attempting to configure or start the server.
You can set up such a server like this (Jetty 6.1.26, servlet-api 2.5):
public void startServer(int port, Servlet yourServletInstance){
Server server = new Server(port);
Context root = new Context(server, "/", Context.SESSIONS);
root.addServlet(new ServletHolder(yourServletInstance), "/servlet/context/path");
//If you need the servlet context for anything, such as spring wiring, you coudl get it like this
//ServletContext servletContext = root.getServletContext();
server.start();
}
Use Selenium for webbased unit tests. There's a Firefox plugin called Selenium IDE which can record actions on the webpage and export to JUnit testcases which uses Selenium RC to run the test server.
First you should probably refactor this a bit so that the DataManager is not created in the doPost code.. you should try Dependency Injection to get an instance. (See the Guice video for a nice intro to DI.). If you're being told to start unit testing everything, then DI is a must-have.
Once your dependencies are injected you can test your class in isolation.
To actually test the servlet, there are other older threads that have discussed this.. try here and here.
public class WishServletTest {
WishServlet wishServlet;
HttpServletRequest mockhttpServletRequest;
HttpServletResponse mockhttpServletResponse;
#Before
public void setUp(){
wishServlet=new WishServlet();
mockhttpServletRequest=createNiceMock(HttpServletRequest.class);
mockhttpServletResponse=createNiceMock(HttpServletResponse.class);
}
#Test
public void testService()throws Exception{
File file= new File("Sample.txt");
File.createTempFile("ashok","txt");
expect(mockhttpServletRequest.getParameter("username")).andReturn("ashok");
expect(mockhttpServletResponse.getWriter()).andReturn(new PrintWriter(file));
replay(mockhttpServletRequest);
replay(mockhttpServletResponse);
wishServlet.doGet(mockhttpServletRequest, mockhttpServletResponse);
FileReader fileReader=new FileReader(file);
int count = 0;
String str = "";
while ( (count=fileReader.read())!=-1){
str=str+(char)count;
}
Assert.assertTrue(str.trim().equals("Helloashok"));
verify(mockhttpServletRequest);
verify(mockhttpServletResponse);
}
}