Spring Test 404 Redirecting - spring-boot

I have an application that redirects all invalid paths to the swagger page like this:
#Configuration
public class MyProjectContext implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController(Endpoints.NOT_FOUND)
.setViewName("redirect:" + Endpoints.SWAGGER);
}
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> containerCustomizer() {
return container -> {
container.addErrorPages(
new ErrorPage(HttpStatus.NOT_FOUND, Endpoints.NOT_FOUND)
);
};
}
}
Now I would like to test that. For that, I have written a test like this:
#AutoConfigureTestEntityManager
#SpringBootTest
#ContextConfiguration(classes = { MyProjectContext.class })
#AutoConfigureMockMvc
class MyProjectErrorControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
void errorEnpointShouldRedirectToSwaggerPage() throws Exception {
mockMvc.perform(get("/dfhdfth"))
.andExpect(redirectedUrl(Endpoints.SWAGGER));
}
}
However, that test currently fails for the following reason:
java.lang.AssertionError: Redirected URL expected:</swagger-ui/index.html> but was:<null>
Also, here's the console output of the Request and Response:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /dfhdfth
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I could imagine this is because of my use of MockMvc here. Is that the case? And if so, how do I write this "properly"?

Okay, I now managed to get it to work as follows:
First, I added the webflux dependency to my build.gradle as follows:
testImplementation 'org.springframework.boot:spring-boot-starter-webflux'
Then, I wrote the tests using the WebTestClient like this:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.reactive.server.WebTestClient;
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
class ErrorControllerTest {
#Autowired
private WebTestClient webTestClient;
#Test
void errorEnpointShouldRedirect() throws Exception {
webTestClient.get()
.uri("/garxms")
.exchange()
.expectStatus()
.isEqualTo(HttpStatus.FOUND);
}
#Test
void errorEnpointShouldRedirectToSwaggerPage() throws Exception {
webTestClient.get()
.uri("/garxms")
.exchange()
.expectHeader()
.valueMatches("Location", ".*" + Endpoints.SWAGGER);
}
}
Works like a charm.

Related

Different response status with Postman and MockMvc when updating entity with PUT method

I have defined enpoint with #RepositoryRestResource:
#Repository
#RepositoryRestResource(collectionResourceRel = "Entities", path = "Entities", itemResourceRel = "Entity")
public interface EntityRepository extends PagingAndSortingRepository<Entity, Long>, QuerydslPredicateExecutor<Entity> {}
Now when I'm executing PUT method against it via Postman I'm getting status 200 OK and response body, but when I'm running integration test locally with MockMVC I'm getting status 204 NO CONTENT.
#AutoConfigureMockMvc
#Retention(RetentionPolicy.RUNTIME)
#Tag("IntegrationTest")
#ActiveProfiles(profiles = {"dev", "test"})
#Testcontainers
#RunWith(SpringRunner.class)
#ContextConfiguration(initializers = ContextInitializer.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class EntityRepositoryTest {
#Autowired
private MockMvc mockMvc;
(...)
#Test
void entityTest() {
// Arrange.
byte[] content = newEntityJsonFile.getInputStream().readAllBytes();
// Act.
ResultActions result = mockMvc.perform(put("/Entities/1").contentType(MediaType.APPLICATION_JSON_VALUE).content(content));
// Assert.
result.andExpect(status().isNoContent());
}
}
#Component
public class RestConfigurer implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(Entity.class);
config.getExposureConfiguration()
.withItemExposure(
(metadata, httpMethods) -> httpMethods.disable(
HttpMethod.DELETE, HttpMethod.PATCH, HttpMethod.PUT, HttpMethod.POST))
.withCollectionExposure(
(metadata, httpMethods) ->
httpMethods.disable(HttpMethod.DELETE, HttpMethod.PATCH, HttpMethod.PUT, HttpMethod.POST));
(...)
config.getExposureConfiguration()
.forDomainType(Entity.class)
.withItemExposure((metadata, httpMethods) -> httpMethods.enable(HttpMethod.PUT, HttpMethod.POST))
.withCollectionExposure((metadata, httpMethods) -> httpMethods.enable(HttpMethod.PUT, HttpMethod.POST));

When using MockMvc to test the controller, a parameter passing error occurred

I am using MockMvc to test the controller. Regarding parameter import, I encountered a type mismatch problem. I tried all the json styles.But nothing works
This is my controller class::
package app.dnatask.controller;
import ......;
#Slf4j
#RestController
#RequestMapping(value = "/API/scanresultconfigure")
public class ScanResultConfigureController extends BaseController {
#RequestMapping(value = "/queryScanResultList/{taskId}/{externalname}", method = RequestMethod.POST)
public IBaseResult queryscanResultList(final HttpServletRequest request, #PathVariable final String taskId, #PathVariable final String externalname, #RequestBody Map map) throws Exception {
return runController(new IControllRunner() {
public void run(IOutResult or, CheckResult cr) throws Exception {
......
}
}
}
}
This is my test class::
package app.dnatask.controller;
import ......
#WebAppConfiguration
#ContextConfiguration(classes = {ScanResultConfigureController.class})
#ComponentScan(
includeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM,
value = {ScanResultConfigureController.class})
},
useDefaultFilters = false,
lazyInit = true
)
public class ScanResultConfigureControllerTest extends AbstractTestNGSpringContextTests {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#BeforeMethod
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).dispatchOptions(true).build();
System.out.println("UT starting.............");
}
#AfterMethod
public void am() {
System.out.println("UT ending.............");
}
#Test
public void testQueryscanResultList() throws Exception {
Map<String, String> testMap = new HashMap<>();
testMap.put("key1", "value1");
testMap.put("key2", "value2");
String requestJson = JSONObject.toJSONString(testMap);
mockMvc.perform(
post("/API/scanresultconfigure/queryScanResultList/001/abc")
.contentType(MediaType.APPLICATION_JSON)
.param("map", requestJson)
)
.andExpect(status().isOk())
.andDo(print());
}
}
Error message::
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json' not supported
java.lang.AssertionError: Status expected:<200> but was:<415>
This is a project implemented by springmvc framework, I use TestNG for unit testing.
Regarding my problem, the solution is as follows::
MvcResult mvcResult = mockMvc.perform(
post("/API/scanresultconfigure/queryScanResultList/{taskId}/{externalname}", "123", "abc")
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson)
)
.andExpect(status().isOk())
.andDo(print())
.andReturn();

How to execute junit test case sequentially

I am writing integration test case which is used to create and update a data
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApplication.class, webEnvironment =
SpringBootTest.WebEnvironment.DEFINED_PORT)
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MyIntegrationTest {
private String baseUrl="http://192.168.6.177/api/v1/";
#Autowired
TestRestTemplate restTemplate;
Long createdId; // trying to set ID which is coming after test1_AddData
#Test
public void test1_AddData() throws Exception {
ABC abc = new ABC("Name");
HttpEntity<ABC> requestBodyData = new HttpEntity<>(ABC);
ParameterizedTypeReference<RestTemplateResponseEnvelope<ABC>> typeReference =
new ParameterizedTypeReference<RestTemplateResponseEnvelope<ABC>>() {
};
ResponseEntity<RestTemplateResponseEnvelope<ABC>> response = restTemplate.exchange(
baseUrl + "/presenceType",
HttpMethod.POST, requestBodyData, typeReference);
Assert.assertTrue(HttpStatus.CREATED.equals(response.getStatusCode()));
createdId = response.getBody().getData().getId();
}
#Test
public void test2_updateData() throws Exception {
ABC abc = new ABC("NEW NAME");
System.out.println("------------------------------------------" + createdId); /// it is giving null
HttpEntity<ABC> requestBodyData = new HttpEntity<>(ABC);
ResponseEntity<ABC> response = restTemplate.exchange(
baseUrl + "/presenceType/" + createdId,
HttpMethod.PUT, requestBodyData, ABC.class);
Assert.assertTrue(HttpStatus.OK.equals(response.getStatusCode()));
createdId = response.getBody().getId();
}
}
the output of my execution
------------------------------------------null
What needs to be done to make this execution i.e calling of second function after the exection of first.
NOTE : The code also contains delete method which needs to be called after third.
Although it is not good practice to fix order in Tests. But If you are using JUnit above version 4.11, it has annotation #FixMethodOrder.
You can set order by method names.
Example:
import org.junit.runners.MethodSorters;
import org.junit.FixMethodOrder;
import org.junit.Test;
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OrderTest {
#Test
public void test1() {
System.out.println("test1");
}
#Test
public void test2() {
System.out.println("test2");
}
}
For further reading #FixMethodOder
Junit Git Page: https://github.com/junit-team/junit4/wiki/test-execution-order
Custom Implemenation: https://memorynotfound.com/run-junit-tests-method-order/
FixMethodOrder not working for me, so I find another way to do this.
We can use #TestMethodOrder from Junit jupiter
https://junit.org/junit5/docs/5.5.0/api/org/junit/jupiter/api/TestMethodOrder.html
#Order annotation used to specify the sequence
Example code:
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
#TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class DummyTest1 {
#Test
#Order(2)
public void read(){
System.out.println("read");
}
#Test
#Order(4)
public void delete(){
System.out.println("delete");
}
#Test
#Order(1)
public void create(){
System.out.println("create");
}
#Test
#Order(3)
public void update(){
System.out.println("update");
}
}
It will execute in sequence order order1, order2, order3, order4
So testing order will be,
create
read
update
delete

No headers when using Mock RestEasy framework

I am trying to use the server-side resteasy mock framework to make a GET request however when trying to retrieve the header from the server code it simply doesn't exist.
Below is the Test class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:base-spring-context.xml" }, loader = DelegatingSmartContextLoader.class)
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class TestClass {
// Sets up RESTEasy processors to test #Provider classes
#Configuration
static class ContextConfiguration {
#Autowired
private ApplicationContext context;
#Bean
public Dispatcher getDispatcher() {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addResourceFactory(getSpringResourceFactory());
return dispatcher;
}
#Bean
public SpringBeanProcessor getRSPostProcessor() {
SpringBeanProcessor processor = new SpringBeanProcessor(getDispatcher(), getDispatcher().getRegistry(),
getDispatcher().getProviderFactory());
return processor;
}
#Bean
public SpringResourceFactory getSpringResourceFactory() {
SpringResourceFactory noDefaults = new SpringResourceFactory("restClass", context,
RestClass.class);
return noDefaults;
}
}
#Autowired
Dispatcher dispatcher;
#Autowired
private ServletContext servletContext;
#Before
public void setUp() {
ResteasyProviderFactory.getContextDataMap().put(HttpServletRequest.class, new MockHttpServletRequest(servletContext));
}
#Test
public void testRest() throws URISyntaxException, ECUNotFoundException {
MockHttpRequest request = MockHttpRequest.get("/")
.header("someHeader", "VALUE")
.accept("application/myValue+XML");
logger.info("HEADERS: {}",request.getHttpHeaders().getRequestHeader("someHeader"));
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
logger.info("Got response: \n\tStatus: '{}'\n\tResponse body: '{}'",response.getStatus(),new String(response.getOutput()));
}
}
Here is the method the Rest method gets triggered from RestClass
#GET
#Path("/")
#Produces("application/myValue+XML")
#GZIP
public Response getMethod(#Context HttpServletRequest request) {
String header = request.getHeader("someHeader");
logger.info("HEADER NAME: {}","someHeader");
if (header == null || header.isEmpty()) {
logger.warn("the header must be present in the request");
return Response.status(Status.BAD_REQUEST).build();
}
The issue here is that the Rest method does not receive the header. It is null however when the printout from the test method clearly shows that the header is set.
Can anybody help understanding why this happens.

Using Spring MVC Test to unit test multipart POST request

I have the following request handler for saving autos. I have verified that this works when I use e.g. cURL. Now I want to unit test the method with Spring MVC Test. I have tried to use the fileUploader, but I am not managing to get it working. Nor do I manage to add the JSON part.
How would I unit test this method with Spring MVC Test? I am not able to find any examples on this.
#RequestMapping(value = "autos", method = RequestMethod.POST)
public ResponseEntity saveAuto(
#RequestPart(value = "data") autoResource,
#RequestParam(value = "files[]", required = false) List<MultipartFile> files) {
// ...
}
I want to uplod a JSON representation for my auto + one or more files.
I will add 100 in bounty to the correct answer!
Since MockMvcRequestBuilders#fileUpload is deprecated, you'll want to use MockMvcRequestBuilders#multipart(String, Object...) which returns a MockMultipartHttpServletRequestBuilder. Then chain a bunch of file(MockMultipartFile) calls.
Here's a working example. Given a #Controller
#Controller
public class NewController {
#RequestMapping(value = "/upload", method = RequestMethod.POST)
#ResponseBody
public String saveAuto(
#RequestPart(value = "json") JsonPojo pojo,
#RequestParam(value = "some-random") String random,
#RequestParam(value = "data", required = false) List<MultipartFile> files) {
System.out.println(random);
System.out.println(pojo.getJson());
for (MultipartFile file : files) {
System.out.println(file.getOriginalFilename());
}
return "success";
}
static class JsonPojo {
private String json;
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
}
}
and a unit test
#WebAppConfiguration
#ContextConfiguration(classes = WebConfig.class)
#RunWith(SpringJUnit4ClassRunner.class)
public class Example {
#Autowired
private WebApplicationContext webApplicationContext;
#Test
public void test() throws Exception {
MockMultipartFile firstFile = new MockMultipartFile("data", "filename.txt", "text/plain", "some xml".getBytes());
MockMultipartFile secondFile = new MockMultipartFile("data", "other-file-name.data", "text/plain", "some other type".getBytes());
MockMultipartFile jsonFile = new MockMultipartFile("json", "", "application/json", "{\"json\": \"someValue\"}".getBytes());
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
mockMvc.perform(MockMvcRequestBuilders.multipart("/upload")
.file(firstFile)
.file(secondFile)
.file(jsonFile)
.param("some-random", "4"))
.andExpect(status().is(200))
.andExpect(content().string("success"));
}
}
And the #Configuration class
#Configuration
#ComponentScan({ "test.controllers" })
#EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport {
#Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver;
}
}
The test should pass and give you output of
4 // from param
someValue // from json file
filename.txt // from first file
other-file-name.data // from second file
The thing to note is that you are sending the JSON just like any other multipart file, except with a different content type.
The method MockMvcRequestBuilders.fileUpload is deprecated use MockMvcRequestBuilders.multipart instead.
This is an example:
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;
/**
* Unit test New Controller.
*
*/
#RunWith(SpringRunner.class)
#WebMvcTest(NewController.class)
public class NewControllerTest {
private MockMvc mockMvc;
#Autowired
WebApplicationContext wContext;
#MockBean
private NewController newController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wContext)
.alwaysDo(MockMvcResultHandlers.print())
.build();
}
#Test
public void test() throws Exception {
// Mock Request
MockMultipartFile jsonFile = new MockMultipartFile("test.json", "", "application/json", "{\"key1\": \"value1\"}".getBytes());
// Mock Response
NewControllerResponseDto response = new NewControllerDto();
Mockito.when(newController.postV1(Mockito.any(Integer.class), Mockito.any(MultipartFile.class))).thenReturn(response);
mockMvc.perform(MockMvcRequestBuilders.multipart("/fileUpload")
.file("file", jsonFile.getBytes())
.characterEncoding("UTF-8"))
.andExpect(status().isOk());
}
}
Have a look at this example taken from the spring MVC showcase, this is the link to the source code:
#RunWith(SpringJUnit4ClassRunner.class)
public class FileUploadControllerTests extends AbstractContextControllerTests {
#Test
public void readString() throws Exception {
MockMultipartFile file = new MockMultipartFile("file", "orig", null, "bar".getBytes());
webAppContextSetup(this.wac).build()
.perform(fileUpload("/fileupload").file(file))
.andExpect(model().attribute("message", "File 'orig' uploaded successfully"));
}
}
Here's what worked for me, here I'm attaching a file to my EmailController under test. Also take a look at the postman screenshot on how I'm posting the data.
#WebAppConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = EmailControllerBootApplication.class
)
public class SendEmailTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Test
public void testSend() throws Exception{
String jsonStr = "{\"to\": [\"email.address#domain.com\"],\"subject\": "
+ "\"CDM - Spring Boot email service with attachment\","
+ "\"body\": \"Email body will contain test results, with screenshot\"}";
Resource fileResource = new ClassPathResource(
"screen-shots/HomePage-attachment.png");
assertNotNull(fileResource);
MockMultipartFile firstFile = new MockMultipartFile(
"attachments",fileResource.getFilename(),
MediaType.MULTIPART_FORM_DATA_VALUE,
fileResource.getInputStream());
assertNotNull(firstFile);
MockMvc mockMvc = MockMvcBuilders.
webAppContextSetup(webApplicationContext).build();
mockMvc.perform(MockMvcRequestBuilders
.multipart("/api/v1/email/send")
.file(firstFile)
.param("data", jsonStr))
.andExpect(status().is(200));
}
}
If you are using Spring4/SpringBoot 1.x, then it's worth mentioning that you can add "text" (json) parts as well . This can be done via MockMvcRequestBuilders.fileUpload().file(MockMultipartFile file) (which is needed as method .multipart() is not available in this version):
#Test
public void test() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.fileUpload("/files")
// file-part
.file(makeMultipartFile( "file-part" "some/path/to/file.bin", "application/octet-stream"))
// text part
.file(makeMultipartTextPart("json-part", "{ \"foo\" : \"bar\" }", "application/json"))
.andExpect(status().isOk())));
}
private MockMultipartFile(String requestPartName, String filename,
String contentType, String pathOnClassPath) {
return new MockMultipartFile(requestPartName, filename,
contentType, readResourceFile(pathOnClasspath);
}
// make text-part using MockMultipartFile
private MockMultipartFile makeMultipartTextPart(String requestPartName,
String value, String contentType) throws Exception {
return new MockMultipartFile(requestPartName, "", contentType,
value.getBytes(Charset.forName("UTF-8")));
}
private byte[] readResourceFile(String pathOnClassPath) throws Exception {
return Files.readAllBytes(Paths.get(Thread.currentThread().getContextClassLoader()
.getResource(pathOnClassPath).toUri()));
}
}

Resources