Spring REST mock context path - java

I try to set the context path for spring rest mocks using the following code snippet:
private MockMvc mockMvc;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation))
.alwaysDo(document("{method-name}/{step}/",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint())))
.build();
}
#Test
public void index() throws Exception {
this.mockMvc.perform(get("/").contextPath("/api").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("_links.business-cases", is(notNullValue())));
}
But I receive the following error:
java.lang.IllegalArgumentException: requestURI [/] does not start with contextPath [/api]
What is wrong?
Is it possible to specify the contextPath at a single place in code e.g. directly in the builder?
edit
here the controller
#RestController
#RequestMapping(value = "/business-case", produces = MediaType.APPLICATION_JSON_VALUE)
public class BusinessCaseController {
private static final Logger LOG = LoggerFactory.getLogger(BusinessCaseController.class);
private final BusinessCaseService businessCaseService;
#Autowired
public BusinessCaseController(BusinessCaseService businessCaseService) {
this.businessCaseService = businessCaseService;
}
#Transactional(rollbackFor = Throwable.class, readOnly = true)
#RequestMapping(value = "/{businessCaseId}", method = RequestMethod.GET)
public BusinessCaseDTO getBusinessCase(#PathVariable("businessCaseId") Integer businessCaseId) {
LOG.info("GET business-case for " + businessCaseId);
return businessCaseService.findOne(businessCaseId);
}
}

You need to include the context path in the path that you're passing to get.
In the case you've shown in the question, the context path is /api and you want to make a request to / so you need to pass /api/ to get:
#Test
public void index() throws Exception {
this.mockMvc.perform(get("/api/").contextPath("/api").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("_links.business-cases", is(notNullValue())));
}

What many people do is simply not use the context path in mockMvc tests. You only specify the #RequestMapping URI:
#Test
public void index() throws Exception {
this.mockMvc.perform(get("/business-case/1234").accept(MediaTypes.HAL_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("_links.business-cases", is(notNullValue())));
}

Related

SpringBoot Unit test - Service method is not invoked

I am new to Unit Testing. After referring google, I created a Test class to test my Controller as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(PromoController.class)
public class PromoApplicationTests {
#Autowired
protected MockMvc mvc;
#MockBean PromoService promoService;
protected String mapToJson(Object obj) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(obj);
}
protected <T> T mapFromJson(String json, Class<T> clazz)
throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, clazz);
}
#Test
public void applyPromotionTest_1() throws Exception {
String uri = "/classPath/methodPath";
List<Cart> cartLs = new ArrayList<Cart>();
// added few objects to the list
String inputJson = mapToJson(cartLs);
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(uri)
.contentType(MediaType.APPLICATION_JSON).content(inputJson)).andReturn();
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
String actual = mvcResult.getResponse().getContentAsString();
String expected = "{\"key1\":val1, \"key2\":\"val 2\"}";
assertEquals(expected, actual, true);
}
}
I have the following Controller and service Class :
#RequestMapping("/classPath")
#RestController
public class PromoController {
#Autowired
PromoService promoService;
#PostMapping("/methodPath")
public PromoResponse applyPromo(#RequestBody List<Cart> cartObj) {
PromoResponse p = promoService.myMethod(cartObj);
return p;
}
}
#Component
public class PromoServiceImpl implements PromoService{
#Override
public PromoResponse myMethod(List<Cart> cartList) {
// myCode
}
}
When I debugged my unit test, p object in the controller was null.
I am getting status as 200 but not the expected JSON response
What am I missing here?
While using #WebMvcTest spring boot will only initialize the web layer and will not load complete application context. And you need to use #MockBean to create and inject mock while using #WebMvcTest.
Which you have done
#MockBean
PromoService promoService;
Spring Boot instantiates only the web layer rather than the whole context
We use #MockBean to create and inject a mock for the GreetingService (if you do not do so, the application context cannot start), and we set its expectations using Mockito.
But the, since it is mock bean you are responsible to mock the myMethod() call
#Test
public void applyPromotionTest_1() throws Exception {
String uri = "/classPath/methodPath";
List<Cart> cartLs = new ArrayList<Cart>();
// added few objects to the list
// create PromoResponse object you like to return
When(promoService.myMethod(ArgumentsMatchesr.anyList())).thenReturn(/*PromoResponse object */);
String inputJson = mapToJson(cartLs);
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.post(uri)
.contentType(MediaType.APPLICATION_JSON).content(inputJson)).andReturn();
int status = mvcResult.getResponse().getStatus();
assertEquals(200, status);
String actual = mvcResult.getResponse().getContentAsString();
String expected = "{\"key1\":val1, \"key2\":\"val 2\"}";
assertEquals(expected, actual, true);
}

Mocking of Resttemplate failed in spring boot junit

I am writing junit test cases for the method which is call an rest api,following is the code I have tried:
#RunWith(MockitoJUnitRunner.class)
public class NotificationApiClientTests {
#Mock
private RestTemplate restTemplate;
#InjectMocks
private NotificationApiClient notificationApiClient;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(notificationApiClient, "notificationUrl", "myURL***");
}
#Test
public void test_NotificationClickAPI_Call() throws JsonParseException, JsonMappingException, IOException {
ResponseEntity<NotificationClickEvent[]> notificationClickEventList = util.getValidNotificationEvent_ResponseEntity();
Mockito.when(restTemplate.exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<NotificationClickEvent[]>> any()
)
).thenReturn(notificationClickEventList);
NotificationClickEvent[] notificationArray = notificationApiClient.requestNotificationClick(Const.NotificationClick, "2018-07-31-10");
assertTrue(notificationArray.length>0);
}
}
and in My NotificationApiClient , it was:
#Value("${notification.base.url}")
private String notificationUrl;
public NotificationApiClient() {
}
public UserInfoEvent[] requestUserInfo(String eventType, String dateStr) {
HttpEntity request = new HttpEntity(setHttpHeaders());
ResponseEntity<UserInfoEvent[]> response = this.exchange(
notificationUrl + eventType + "&dateStr=" + dateStr,
HttpMethod.GET, request, UserInfoEvent[].class);
UserInfoEvent[] userInfoRequest = response.getBody();
return userInfoRequest;
}
but it's not working, as per my code whenever the resttemplate.exchange method is called it should return the notificationClickEventList, But its calling the real api and returns the api result as the list.
Can anyone please help me to solve it?
In your code you are not using restTemplate.exchange method, It seems you are using notificationApiClient's exchange method. So try this.
#Spy
#InjectMocks
private NotificationApiClient notificationApiClient;
Mockito.when(notificationApiClient.exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<NotificationClickEvent[]>> any()
)
).thenReturn(notificationClickEventList);

Spring MockMVC test with JSON content is throwing NullPointerException from Spring code

I have a small Spring MVC project. I'm writing MockMvc tests for it. Most of them work, but this one (the first one I've tried with a plain JSON body) is giving me trouble. I keep getting a NullPointerException from deep within Spring. I tried debugging through it, but eventually ran out of attached Spring source code without getting any closer to an answer.
My JSON block is captured from a live user test, which works fine. But in the test, it throws NPE. If I modify the JSON block to be malformed (IE, add an extra comma somewhere), then it throws a 400 Bad Request, as expected. Remove the extra comma, go back to NPE. Making the block invalid (IE, making a field null which is marked #NotNull in my domain object) does not give the expected 400 Bad Request. It just stays with the NPE.
All my other tests so far have been for controllers which just use query string params, and have worked fine. Also, I have one which due to browser restrictions on our customer's side must embed its JSON in a POST param (IE, "json = "{blah:blah}"), which I pull out and manually parse. That works fine, too.
Controller:
#RestController
public class SaveController {
#Autowired
private MyDao myDao;
#RequestMapping(value = "/path/to/controller", method = RequestMethod.POST)
#PreAuthorize("hasAnyRole('myRole', 'myAdminRole')")
public void updateThing(#Valid #RequestBody MyThing myThing) throws IOException {
myDao.updateMyThing(myThing);
}
}
Base test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = {TestDataAccessConfiguration.class, TestApplicationConfiguration.class})
public abstract class AbstractSpringTestCase {
#Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
MockitoAnnotations.initMocks(this);
}
}
Test:
public class SaveControllerTest extends AbstractSpringTestCase {
#Mock
private MyDao myDao;
#InjectMocks
#Autowired
private SaveController classUnderTest;
private static final JSON = "<a big JSON string captured from (working) production>";
#Test
public void testHappyPath() throws Exception {
mockMvc.perform(post("/path/to/controller")
.contentType(MediaType.APPLICATION_JSON)
.content(JSON))
.andExpect(status().isOk());
}
}
Stacktrace:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:170)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:137)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:145)
at SaveControllerTest.testHappyPath(SaveControllerTest.java)
In order to send a request body using Spring MockMvc you must map the #RequestBody object as a json string.
Here's an example:
SingupController:
#PostMapping("/signup")
public #ResponseBody
RealityKeeper createUser(#RequestBody SignupRequest signupRequest) {
System.out.println("SignupRequest: " + signupRequest);
String password = signupRequest.getPassword();
String username = signupRequest.getUsername();
String encoded = passwordEncoder.encode(password);
RealityKeeper realityKeeper = new RealityKeeper(username, encoded);
return repository.save(realityKeeper);
}
SignupControllerTest:
#Test
public void createUser() throws Exception {
SignupRequest signupRequest = new SignupRequest("foo", "bar");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String requestJson=ow.writeValueAsString(signupRequest);
mockMvc.perform(post("/api/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson))
.andExpect(MockMvcResultMatchers.status().isOk());
}
Using jackson's object mapper you can turn a pojo into a json string and pass it to the mockMvc content method.
Hope this helps

A spring integration test

In my integration test, I tried to use resttemplate to send a Get request to a dummy server created by MockMvcBuilders. However I got an error:
I/O error on GET request for "http://localhost:8080/test":Connection refused:
(In the function testAccess(), url is "http://localhost:8080/test"). My code is as below:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port=8080")
public class MyTest {
private MockMvc mockMvc = null;
#Autowired
private WebApplicationContext context;
#Value("${server.port}")
private int port;
#Autowired
private MyController myController;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
#Test
public void testAccess() throws Exception{
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/test";
try{
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
}
catch(ResourceAccessException ex){
System.out.println(ex.getMessage());
}
}
#Controller
public static class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public #ResponseBody String access() {
return "OK";
}
}
}
The way I've done it is this:
First, you create a mock server from the actual RestTemplate you are using in your application
#Before
public void setup() throws Exception {
mockServer = MockRestServiceServer.createServer(myService.restTemplate);
}
Then you define how that request is going to work:
mockServer.expect(requestTo("http://localhost:8080/myrestapi"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("{ success: true }", MediaType.APPLICATION_JSON));
And last you call the method in your application that will trigger a call to that url with that RestTemplate:
#Test
public void testThis() throws Exception {
myService.somethingThatCallsMyRestApi(parameters);
}
That will make your tests work as if there was a server up and running to process requests.
Using this in your example makes no sense, cause you would be testing that you build your test correctly and nothing else from the actual application.
The problem with this is that you cannot test dynamic responses. I mean, in my case the method I'm calling generates different data every time you call it and then sends it to the mockServer and then validates that the response matches in some very specific way. I haven't found a solution yet, but if the data you are going to send and receive is previously known, which would be in most cases, you'll have no problem using this.
Why are you defining a controller in your Test class and then trying to test it ? It doesn't feel logical to try to test something that is defined within the test it self.
Rather you would want to test a controller defined somewhere outside your tests, an actual controller that is used within your application.
Let's say MyController is defined as an actual controller then you could use the mockMvc object you created to test it.
mockMvc.perform(get('/test'))
.andExpect(status().isOk())

java.lang.AssertionError: Content type not set - Spring Controller Junit Tests

I am trying to do some unit testing on my controllers. No matter what I do all controller tests return
java.lang.AssertionError: Content type not set
I am testing that the methods return json and xml data.
Here is an example of the controller:
#Controller
#RequestMapping("/mypath")
public class MyController {
#Autowired
MyService myService;
#RequestMapping(value="/schema", method = RequestMethod.GET)
public ResponseEntity<MyObject> getSchema(HttpServletRequest request) {
return new ResponseEntity<MyObject>(new MyObject(), HttpStatus.OK);
}
}
The unit test is set up like this:
public class ControllerTest() {
private static final String path = "/mypath/schema";
private static final String jsonPath = "$.myObject.val";
private static final String defaultVal = "HELLO";
MockMvc mockMvc;
#InjectMocks
MyController controller;
#Mock
MyService myService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = standaloneSetup(controller)
.setMessageConverters(new MappingJackson2HttpMessageConverter(),
new Jaxb2RootElementHttpMessageConverter()).build();
when(myService.getInfo(any(String.class))).thenReturn(information);
when(myService.getInfo(any(String.class), any(Date.class))).thenReturn(informationOld);
}
#Test
public void pathReturnsJsonData() throws Exception {
mockMvc.perform(get(path).contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath(jsonPath).value(defaultVal));
}
}
I am using:
Spring 4.0.2
Junit 4.11
Gradle 1.12
I have seen the SO question Similiar Question but no matter what combination of contentType and expect in my unit test I get the same result.
Any help would be much appreciated.
Thanks
Your solution depends on what kinds of annotation you want to use in your project.
You can add #ResponseBody to your getSchema method in Controller
Or, maybe adding produces attribute in your #RequestMapping can solve it too.
#RequestMapping(value="/schema",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE} )
Final choice, add headers to your ResponseEntity (which is one of the main objective of using this class)
//...
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<MyObject>(new MyObject(), headers, HttpStatus.OK);
Edit : I've just seen you want Json AND Xml Data, so the better choice would be the produces attribute:
#RequestMapping(value="/schema",
method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE} )
You need to add
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET
value = "/schema")
And <mvc:annotation-driven />in your xml config or #EnableWebMvc

Categories