I have the next unit test define to test a controller which is used to upload files:
public class PhenotypeControllerTest extends BaseControllerTest{
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
#Before
public void setup() throws Exception {
super.setup();
}
#Test
public void loadPhenotype_success() throws Exception{
//mock uuid generation
UUID idFile = UUID.randomUUID();
//Generate the response
ResponseLoad resp = new ResponseLoad();
resp.setFileIdentifier(idFile);
resp.setStatus(Status.FINISHED);
resp.setDescription(null);
MockMultipartFile phenoFile = new MockMultipartFile("size_trans_20160419_KM2.txt","size_trans_20160419_KM2.txt", ContentType.TEXT_PLAIN.toString(), new FileInputStream("src/test/resources/size_trans_20160419_KM2.txt"));
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/phenotypes/load")
.file(phenoFile))
.andExpect(status().isOk())
.andExpect(content().contentType(this.contentType))
.andExpect(content().json(json(resp)));
}
}
The super class of the test contains the annotations:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#WebAppConfiguration
#TestPropertySource(locations="classpath:application.test.properties")
public abstract class BaseControllerTest {
protected MockMvc mockMvc;
#SuppressWarnings("rawtypes")
protected HttpMessageConverter mappingJackson2HttpMessageConverter;
#Autowired
protected WebApplicationContext webApplicationContext;
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream()
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#SuppressWarnings("unchecked")
protected String json(Object o) throws IOException {
MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
this.mappingJackson2HttpMessageConverter.write(
o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
When I run test I get an 400 error but other tests which uses a non multipart request works fine.The controller method is like:
#ApiOperation(value = "Load Phenotype File", nickname = "loadPhenotype",
tags = {"Phenotypes"} )
#ApiResponses({
#ApiResponse(code = 200, message = "Nice!", response = Response.class),
#ApiResponse(code = 507, message = "Error uploading files")
})
#PostMapping(value="/phenotypes/load", produces = "application/json")
public ResponseEntity<ResponseLoad> uploadPhenotype(
#ApiParam(value="Phenotype File", required=true)
#RequestPart(required = true) MultipartFile file){
//1. Validate parameters
ResponseLoad response = new ResponseLoad();
response.setStatus(Status.FINISHED);
//2. Copy file to /tmp/SNPaware/phenotypes/tmp/<UUID>.pheno
response.setFileIdentifier(UUID.randomUUID());
logger.info("Storage phenotype file with identifier "+response.getFileIdentifier());
storageService.store(file, "tmp/"+response.getFileIdentifier()+".pheno");
return ResponseEntity.ok(response);
}
}
And it works correctly when I send a request to the rest api like this:
curl -X POST --header 'Content-Type: multipart/form-data' --header
'Accept: application/json' {"type":"formData"}
'http://hippo:9087/phenotypes/load'
Why I am receiving a 400 in the test? Am I missing some configuration on the test?
The problem was in the definition of the multipartFile in the test. The original name should match the name of the parameter in the controller, in this case file.
This definition solve the problem:
MockMultipartFile phenoFile = new MockMultipartFile("file", "size_trans_20160419_KM2.txt", ContentType.TEXT_PLAIN.toString(), new FileInputStream("src/test/resources/size_trans_20160419_KM2.txt"));
Related
Controller:
#ApiOperation(value = " update record", response = APIResponse.class)
#ApiResponses(value = {#ApiResponse(code =200, message = "OK"),
#ApiResponses(value = {#ApiResponse(code =500, message = "Internal server error"),
#ApiResponses(value = {#ApiResponse(code =404, message = "NO_RECORD")})
#PutMapping(value = "/update/{id}")
#ResponseBody
public ResponseEntity<APIResponse> updateRecord(HttpServletRequest request, #RequestBody RecordDTO input, #PathVariable(value="id") int code){
APIResponse response = null;
try{
response = service.updateRecord(code, input);
}
catch(JSONException e){
log.error("Error Parsing JSON");
response = new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, ERROR_JSON_PARSING, ERROR);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
my test case foor controller:
#Test
public void update() throws Exception{
RecordDTO recordDto = new RecordDTO();
Object mapper = new ObjectMapper();
String value = mapper.writeValueAsString(StationDTO);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willThrow(JSONException.class);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isInternalservererror())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("INTERNAL_SERVER_ERROR")))
.andExpect(jsonPath("$.message",Matchers.is("ERROR_JSON_PARSING")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("ERROR")));
APIResponse response = new APIResponse(HttpStatus.OK, SUCCESS, SUCCESS, null);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willReturn(response);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("OK")))
.andExpect(jsonPath("$.message",Matchers.is("SUCCESS")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("SUCCESS")));
}
DTO:
public class RecordDTO{
private String id;
private String name;
private String number;
}
I am getting java.lang assertion error expected 500 but was 200. I don't what is wrong with the test case.. Is there any other way to write the test case? Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down. Thanks for the help!
Seems like your mocked service is not injecting into your controller.
Alternative solution (I assume you use Spring-Boot):
DisableAutowireRequireInitializer. This will prevent to load all dependencies inside your Controller.
Create inside your ControllerTest inner class: private static ServiceImplMock entends ServiceImpl
Now, override updateRecord method inside ServiceMock to do your testing cases
#Override
public APIResponse updateRecord(int code, RecordDTO input) throws JSONException {
if(code == 12) throw new JSONException(...)
else your_business_logic
}
Now, add this ServiceImplMock into your #SpringBootTest
#SpringBootTest(classes = {
Controller.class,
ControllerTest.ServiceImplMock.class,
...
})
#AutoConfigureMockMvc
#ContextConfiguration( initializers = {DisableAutowireRequireInitializer.class })
class ControllerTest {
Now, your test cases should work (Remove given(...).willThrow(...); since we don't need it anymore)
Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down
https://www.baeldung.com/junit
https://www.baeldung.com/spring-boot-testing
https://mkyong.com/spring-boot/spring-boot-junit-5-mockito/
I am currently writing a JUnit test case for a controller in my application which returns a object (a URL). I am trying to assert the expected and the actual URL to be the same. There are 2 things happening here when I inspect the MvcResult result:
mockResponse has a status code of 200.
In ModelAndView, the model does have the expected url value but when I try to assert the result using result.getResponse().getContentAsString(),
the assertion fails as the result is empty.
What I have already tried:
While debugging, I see the control moving to the service which means that the values were properly mocked and the expected url got returned to the result (as it was present in the ModelAndView when inspected).
I have tried to give the expected url as a json object, used object mapper to read it and then tried a JSONAssert but the result is still empty.
#RunWith(SpringJUnit4ClassRunner.class)
public class StudentControllerTest {
private static final String CACHE_URL= "cacheurl";
#Mock
StudentCacheService studentCacheService;
#InjectMocks
StudentCacheController studentCacheController;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(studentCacheController).build();
}
#Test
public void testGetScoresUrl() throws Exception {
Mockito.when(studentCacheService.getStudentUrl("123", "science"))
.thenReturn(new StudentUrl(CACHE_URL));
MvcResult result = this.mockMvc.perform(MockMvcRequestBuilders.get("/student/123/scores")
.header("subject", "science").contentType(MediaType.APPLICATION_JSON)).andExpect(status().is2xxSuccessful())
.andReturn();
Assert.assertEquals(CACHE_URL, result.getResponse().getContentAsString());
}
}
My Controller class is as below:
#Controller
#RequestMapping("/student")
public class StudentCacheController {
#Autowired
StudentCacheService studentCacheService;
#GetMapping(path = "/{studentId}/scores",produces = MediaType.APPLICATION_JSON_VALUE)
public StudentUrl getScores(#PathVariable String studentId, #RequestHeader(value = "subject", required = true) String subject) throws Exception {
return studentCacheService.getStudentUrl(studentId, subject);
}
}
The response is as below:
MockHttpServletResponse:
Status = 200
Error message = null
Forwarded URL = student/123/scores
Included URL = []
ModelAndView:
model = ModelMap
key = studentUrl
value = StudentUrl
url = "cacheurl"
I am receiving this error : org.junit.ComparisonFailure: expected:<[cacheurl]> but was:<[]>
Any help appreciated. Thanks!
I have this endpoint for Spring Rest API:
#PostMapping(value = "/v1/", consumes = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE })
public PaymentResponse handleMessage(#RequestBody PaymentTransaction transaction, HttpServletRequest request) throws Exception {
// get here plain XML
}
XML model.
#XmlRootElement(name = "payment_transaction")
#XmlAccessorType(XmlAccessType.FIELD)
public class PaymentTransaction {
public enum Response {
failed_response, successful_response
}
#XmlElement(name = "transaction_type")
public String transactionType;
.........
}
How I can get the XML request in plain XML text?
I also tried with Spring interceptor:
I tried this code:
#SpringBootApplication
#EntityScan("org.plugin.entity")
public class Application extends SpringBootServletInitializer implements WebMvcConfigurer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
........
#Bean
public RestTemplate rsestTemplate() {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
RestTemplate restTemplate = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
Component for logging:
#Component
public class RestTemplateHeaderModifierInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("[ ");
for (byte b : body) {
sb.append(String.format("0x%02X ", b));
}
sb.append("]");
System.out.println("!!!!!!!!!!!!!!!");
System.out.println(sb.toString());
ClientHttpResponse response = execution.execute(request, body);
InputStream inputStream = response.getBody();
String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
System.out.println("!!!!!!!!!!!!!!!");
System.out.println(result);
return response;
}
}
But nothing is printed into the console. Any idea where I'm wrong? Probably this component is not registered?
Shouldn't it be easy like below to get it from HttpServletRequest, unless I'm missing something. I don't think there is need to use interceptor etc.
#PostMapping(value = "/v1/", consumes = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE,
MediaType.APPLICATION_JSON_VALUE })
public PaymentResponse handleMessage(HttpServletRequest request) throws Exception {
String str, wholeXML = "";
try {
BufferedReader br = request.getReader();
while ((str = br.readLine()) != null) {
wholeXML += str;
}
System.out.println(wholeXML);
//Here goes comment question, to convert it into PaymentTransaction
JAXBContext jaxbContext = JAXBContext.newInstance(PaymentTransaction.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(wholeXML);
PaymentTransaction paymentTransaction = (PaymentTransaction) unmarshaller.unmarshal(reader);
}
We had the same issue and use this solution in production. Which is not framework dependent (always an upside in my book) and simple.
Just consume it without specifying it as an XML. Then read the request lines and join them by \n if you want to have new lines in your xml. If not, join them by "" or whatever you please. This presumes you are using the javax.servlet.http.HttpServletRequest
Example:
#PostMapping(value = "/v1")
public PaymentResponse handleMessage(HttpServletRequest request) throws Exception {
final InputStream xml = request.getInputStream();
final String xmlString = new BufferedReader(new InputStreamReader(xml))
.lines()
.collect(Collectors.joining("\n"));
// do whatever you please with it
}
And you have an plain xml string.
For your controller to receive the request body as a plain xml string, you need only change the #RequestBody parameter type to String:
#PostMapping(value = "/v1/", consumes = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE })
public PaymentResponse handleMessage(#RequestBody String xmlOrJson, HttpServletRequest request) throws Exception {
...
With the above mapping, if the client has submitted xml, you'll see the raw XML. Otherwise, if the client has submitted json, you'll see the raw JSON. Make sure you check the request's "Content-Type" header to know which type you're dealing with.
See https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-requestbody
We've been using the spring-mvc-logger in production for quite a while. It's written as a servlet filter, so can be added as an independent wrapper to the MVC endpoint.
Our set up is almost exactly like described on the readme.md there, though we restrict the <url-pattern> under the <filter-mapping> to just the useful endpoints.
Even if it's not exactly what you're after, the codebase there makes quite a nice small example. In particular note the request/response wrapping that is needed in the filter. (This is to avoid the IllegalStateException: getReader(), getInputStream() already called that would otherwise happen if getReader() were called twice).
You have created List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>(); but did not add the RestTemplateHeaderModifierInterceptor object to it.
You can autowire in the same in Application like below:
#Autowired
ClientHttpRequestInterceptor clientHttpRequestInterceptor;
and
interceptors.add(clientHttpRequestInterceptor);
The code looks like below:
class Application {
...
#Autowired
ClientHttpRequestInterceptor clientHttpRequestInterceptor;
#Bean
public RestTemplate rsestTemplate() {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
RestTemplate restTemplate = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
interceptors.add(clientHttpRequestInterceptor);
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
...
}
Hope it helps
I'm trying to write contract test to this service:
#RestController
#RequestMapping(path = "/api/form")
public class FormController {
private RestOperations restOperations;
#Autowired
public FormController(RestOperations restOperations) {
this.restOperations = restOperations;
}
#PostMapping(path = "/submit", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<SubmitFormResponse> submitForm(#RequestBody #Valid SubmitFormCommand submitFormCommand) {
return restOperations.postForEntity("http://127.0.0.1:9000/api/form/submit", submitFormCommand, SubmitFormResponse.class);
}
}
SubmitFormCommand contains only String "message" and SubmitFormResponse contains Boolean "success"
My RestClient for this service:
#Component
public class FormControllerClient {
#Autowired
private RestOperations restOperations;
public ResponseEntity<SubmitFormResponse> submitForm(SubmitFormCommand submitFormCommand) {
HttpEntity<SubmitFormCommand> request = new HttpEntity<>(submitFormCommand);
return restOperations.exchange("http://localhost:1234/api/form/submit", HttpMethod.POST, request, SubmitFormResponse.class);
}
And Contract test class of consumer looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ContactFormClientTest {
#Rule
public PactProviderRuleMk2 pactProviderRuleMk2 = new PactProviderRuleMk2("formservice", "localhost", 1234, this);
#Autowired
private FormControllerClient formControllerClient;
#Pact(state = "provider accets submit contact form", provider = "formservice", consumer = "formclient")
public RequestResponsePact submitFormPact(PactDslWithProvider builder) {
return builder
.given("provider accetps form submit")
.uponReceiving("a request to POST form")
.path("/api/form/submit")
.method("POST")
.willRespondWith()
.status(200)
.matchHeader("Content-Type", "application/json;charset=UTF-8")
.body(new PactDslJsonBody()
.stringType("message", "TestMessage"))
.toPact();
}
#Test
#PactVerification(fragment = "submitFormPact")
public void verifySubmitFormPact() {
SubmitFormCommand submitFormCommand = new SubmitFormCommand("TestMessage");
ResponseEntity<SubmitFormResponse> response = formControllerClient.submitForm(submitFormCommand);
assertNotNull(response);
}
}
Every time I run the test it says "Connection refused" and I don't understand what I did wrong with a setup, my FormController would be a consumer in this case since it calls another service to submit the form.
Plugin in pom.xml for building Pact file looks like this :
<plugin>
<!-- mvn pact:publish -->
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.11</artifactId>
<version>3.5.10</version>
<configuration>
<pactDirectory>../pacts</pactDirectory>
<pactBrokerUrl>http://localhost:1234</pactBrokerUrl>
<projectVersion>${project.version}</projectVersion>
</configuration>
</plugin>
The problem is you are placing your request body in the response. Your pact should look like:
#Pact(state = "provider accets submit contact form", provider = "formservice", consumer = "formclient")
public RequestResponsePact submitFormPact(PactDslWithProvider builder) {
return builder
.given("provider accetps form submit")
.uponReceiving("a request to POST form")
.path("/api/form/submit")
.method("POST")
.body(new PactDslJsonBody()
.stringType("message", "TestMessage"))
.willRespondWith()
.status(200)
.matchHeader("Content-Type", "application/json;charset=UTF-8")
.body(new PactDslJsonBody()
.booleanType("sucess", true))
.toPact();
}
I have a Rest controller similar to this one:
#RestController
public class UserRestController {
#Autowired
private UserService userService;
#RequestMapping(value = "/user/activate", method = RequestMethod.POST)
public ResponseEntity<UserDTO> activate(
#RequestParam(required = true) final String email,
#RequestParam(required = true) final String key) {
UserDTO userDTO = userService.activateAccount(email, key);
return new ResponseEntity<UserDTO>(userDTO, HttpStatus.OK);
}
}
When I invoke it using Postman and I don't send the 'key' parameter, I receive this JSON message:
{
"timestamp": 1446211575193,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter 'key' is not present",
"path": "/user/activate"
}
On the other hand, I am testing this method with JUnit and the MockMVC Spring utility.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = ApplicationConfig.class)
#WebAppConfiguration
public class UserRestControllerTest {
private static MockMvc mockMvc;
#Mock
private UserService userService;
#InjectMocks
private UserRestController userRestController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(userRestController)
.setMessageConverters(
new MappingJackson2HttpMessageConverter(),
new Jaxb2RootElementHttpMessageConverter()).build();
}
#Test
public void testActivateRequiredParams() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.post("/user/activate")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(
MockMvcResultMatchers.content().contentType(
UtilsUnitTest.APPLICATION_JSON_UTF8))
.andExpect(
jsonPath(
"message",
is("Required String parameter 'email' is not present")));
}
}
But when I execute this test I notice that the response is not a JSON message. In fact, I get an exception:
java.lang.AssertionError: Content type not set
Particularly, the completed result is
MockHttpServletRequest:
HTTP Method = POST
Request URI = /user/activate
Parameters = {}
Headers = {Content-Type=[application/x-www-form-urlencoded]}
Handler:
Type = com.company.controller.UserRestController
Method = public org.springframework.http.ResponseEntity<com.company.dto.UserDTO> com.company.controller.UserRestController.activate(java.lang.String,java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.bind.MissingServletRequestParameterException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = Required String parameter 'email' is not present
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I deduce that when an exception is thrown this is not converted to JSON format (I get a correct JSON result when I send the email and key parameters)
The question is obvious: what should I change in the Unit Test configuration to get a JSON error message in case of an exception be thrown?
The problem is that the test stops after following assertion fails:
.andExpect(MockMvcResultMatchers.content().contentType(UtilsUnitTest.APPLICATION_JSON_UTF8))
Beside this, jsonPath("message"... won't hit anything. To validate the returned error message use MockMvcResultMatchers.status().reason(<ResultMatcher>).
The following test should do the job:
#Test
public void testActivateRequiredParams() throws Exception {
final ResultActions result = mockMvc
.perform(MockMvcRequestBuilders.post("/user/activate")
.contentType(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print());
result.andExpect(MockMvcResultMatchers.status().isBadRequest());
result.andExpect(MockMvcResultMatchers.status().reason(is("Required String parameter 'email' is not present")));
}
But think about if this (testing for error messages) is a good idea in general, maybe see this discussion.