I'm trying to test my Rest controllers from my Spring Boot application and want the controllers to be available under the same path as in production.
For example I have the following Controller:
#RestController
#Transactional
public class MyController {
private final MyRepository repository;
#Autowired
public MyController(MyRepository repository) {
this.repository = repository;
}
#RequestMapping(value = "/myentity/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resource<MyEntity>> getMyEntity(
#PathVariable(value = "id") Long id) {
MyEntity entity = repository.findOne(id);
if (entity == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(entity, HttpStatus.OK);
}
}
Within my application.yml I have configured the context path for the application:
server:
contextPath: /testctx
My test for this controller looks as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = MyController.class, secure=false)
public class MyControllerTest {
#Autowired
private MyRepository repositoryMock;
#Autowired
private MockMvc mvc;
#Test
public void testGet() throws Exception {
MyEntity entity = new MyEntity();
entity.setId(10L);
when(repositoryMock.findOne(10L)).thenReturn(entity);
MockHttpServletResponse response = this.mvc.perform(
MockMvcRequestBuilders.get("/testctx/myentity/10"))
.andReturn().getResponse();
assertEquals(response.getStatus(), 200);
}
#TestConfiguration
public static class TestConfig {
#Bean
MyRepository mockRepo() {
return mock(MyRepository.class);
}
}
}
This test fails since the status code is 404 for the call. If I call /myentity/10 it works. Unfortunately the rest call is initiated by a CDC-Test-Framework (pact) so I cannot change the requested path (containing the context path /testctx). So is there a way to tell spring boot test to start the rest endpoint with a defined context path also during testing?
You could try:
#WebMvcTest(controllers = {MyController.class})
#TestPropertySource(locations="classpath:application.properties")
class MyControllerTest {
#Autowired
protected MockMvc mockMvc;
#Value("${server.servlet.context-path}")
private String contextPath;
#BeforeEach
void setUp() {
assertThat(contextPath).isNotBlank();
((MockServletContext) mockMvc.getDispatcherServlet().getServletContext()).setContextPath(contextPath);
}
protected MockHttpServletRequestBuilder createGetRequest(String request) {
return get(contextPath + request).contextPath(contextPath)...
}
Related
I need to write unit tests for a Spring Controller class.
The setup is like this:
#RestController
#RequestMapping("/")
public class MyCustomController {
#Autowired
private MagicWriter magicWriter;
#Autowired
private MagicUpdater magicUpdater;
#RequestMapping(path = "/", method = RequestMethod.POST)
public String postMagicMethod(#RequestParam(name = "SomeParam") String param1) {
var magicHandler = new MagicHandler(magicWriter, magicUpdater);
return magicHandler.doSomeMagic();
}
}
From my JUnit test, I need to use #MockBean for magicWriter and magicUpdater class.
So far I could not find anything constructive.
Here is my Unit test
#SpringJUnitConfig
#WebMvcTest(value= MyCustomController.class)
public class MyCustomControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MagicWriter magicWriter;
#MockBean
private MagicUpdater magicUpdater;
#Autowired
private WebApplicationContext webApplicationContext;
#Configuration
static class Config {
#Bean
MyCustomController dispatchController() {
return new MyCustomController();
}
}
#Test
void basicTest() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
HttpHeaders headers = new HttpHeaders();
// Added some http headers
String uri = "/";
RequestBuilder request = MockMvcRequestBuilders.request(HttpMethod.POST, uri, headers);
MvcResult result = mockMvc.perform(request).andReturn();
assertThat(result.getResponse().getContentAsString()).isEqualTo(expected);
}
}
Convert your #Autowired parameters to be constructor based and not field-based.
#RestController
#RequestMapping("/")
public class MyCustomController {
private MagicWriter magicWriter;
private MagicUpdater magicUpdater;
#Autowired
public MyCustomController(MagicWriter magicWriter, MagicUpdater magicUpdater) {
this.magicWriter = magicWriter;
this.magicUpdater = magicUpdater;
}
// ... rest of your code
}
Then in your test, you just new an instance of this class with your mocks passed in. You're already resigned to using mock beans, so you don't need to whole Spring Context to come along.
// Unit test code example
MyCustomController testObject;
MagicWriter magicWriterMock;
magicUpdater magicUpdaterMock;
#BeforeEach
void setUp() throws Exception {
magicWriterMock = mock(MagicWriter.class);
magicUpdaterMock = mock(MagicUpdater.class);
testObject = new MyCustomController(magicWriterMock, magicUpdaterMock);
}
I am trying to write integration test in Spring Boot 2.7.3.
This application should scrape data from some external REST service. To call rest service I am using RestTemplate from spring framework
#Service
public class UserService {
private final RestTemplate restTemplate;
private String url = "http://localhost:3000/user/{name}";
#Autowired
public UserService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public UserData getUserInfo(UserInfo userInfo) {
return restTemplate.getForObject(url, UserData.class, userInfo.getName());
}
}
This service is used by rest controller that is accepting name as a path parameter (we can call it localhost:8080/info?name=joe)
#RestController
public class UserController {
private UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("/info")
#ResponseBody
public UserData getUserData(UserInfo userInfo) {
return userService.getUserInfo(userInfo);
}
}
I have created controller annotated with #RestControllerAdvice and handling method that should be invoked when throwing HttpClientErrorException
#RestControllerAdvice
public class UserExceptionHandler {
#Autowired
public UserExceptionHandler(){
}
#ExceptionHandler(value = {HttpClientErrorException.class})
public ErrorResponse clientErrorHandle(HttpClientErrorException e) {
return ErrorResponse.builder().error("could not retrieve user info").build();
}
}
Problem is that below test is not hitting UserExceptionHandler
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerTest {
#Autowired
RestTemplate restTemplate;
#Autowired
UserController userController;
private MockRestServiceServer mockServer;
#BeforeEach
public void setUp() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testGetRootResourceOnce() {
mockServer.expect(once(), requestTo("http://localhost:3000/user/joe"))
.andRespond(withBadRequest().contentType(MediaType.APPLICATION_JSON).body("{\"error\": \"Error while getting data\"}"));
UserInfo userInfo = UserInfo.builder().name("joe").build();
UserData userData = userController.getUserData(userInfo);
mockServer.verify();
assertThat(userData).isNotNull();
}
}
While debugging I set breakpoint on UserExceptionHandler constructor so I assume that it has been created.
But why method clientErrorHandle was not invoked?
I see that RestTemplate throw HttpClientErrorException (please see picture below) so it should be intercepted by my handler, but it was not
I have a simple Rest Controller as below
#RestController
public class HealthController {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
private HealthService healthService;
#Autowired
public HealthController(HealthService healthService) {
this.healthService = healthService;
}
#RequestMapping(value = "/health", method = RequestMethod.GET)
public ResponseEntity<?> healthCheck() {
return healthService.checkHealth();
}
}
The service class is below
#Service
public class HealthService {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
public ResponseEntity<?> checkHealth() {
logger.info("Inside Health");
if (validateHealth()) {
return new ResponseEntity<>("Healthy", HttpStatus.OK);
} else {
return new ResponseEntity<>("Un Healthy", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
boolean validateHealth() {
return true;
}
}
The corresponding unit test for the controller class as below
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HealthController.class)
public class HealthControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HealthService healthService;
#Test
public void checkHealthReturn200WhenHealthy() throws Exception {
ResponseEntity mockSuccessResponse = new ResponseEntity("Healthy", HttpStatus.OK);
when(healthService.checkHealth()).thenReturn(mockSuccessResponse);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/health").accept(
MediaType.APPLICATION_JSON);
MvcResult healthCheckResult = mockMvc
.perform(requestBuilder).andReturn();
Assert.assertEquals(HttpStatus.OK.value(), healthCheckResult.getResponse().getStatus());
}
}
The problem I have is my CustomLogger. Since it has external dependencies am having issues in trying to test this.The same kind of logger is present in my service classes too.
How can I test such a class. I tried the below stuffs
Created a custom class name CustomLoggerForTest under test. Used
ReflectionTestUtils.setField(healthService, "logger", new CustomerLoggerForTest(HealthService.class.getName()));
in the setUp. But it did not help. Using this we cannot set the static fields hence tried even converting them to be non-static
Tried with mocking the CustomLogger in setup as below
mockStatic(CustomLogger.class); when(CustomLogger.getLogger(any())) .thenReturn(new CustomLoggerForTest(HealthController.class.getName()));
But no luck.
Is there anything that am doing wrong that is causing this?
I am still getting Access Denied although my test method is annotated with #WithMockUser. Why this is not working in integration test? Everything is fine with test with #WebAppConfiguration and MockMvc.
Test Class:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FileUploadIntegrationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private FileStorageService storageService;
#Test
public void classPathResourceTest() throws Exception {
ClassPathResource resource = new ClassPathResource("/test/testFile.txt", getClass());
assertThat(resource.exists(), is(true));
}
#Test
#WithMockUser(username="tester",roles={"USER"})
public void shouldUploadFile() throws Exception {
ClassPathResource resource = new ClassPathResource("/test/testFile.txt", getClass());
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("file", resource);
ResponseEntity<String> response = this.restTemplate.postForEntity("/files", map, String.class);
// assertThat(response.getStatusCode(), is(HttpStatus.OK));
then(storageService).should().addFile((any(String.class)), any(MultipartFile.class));
}
}
Controller class:
#RestController
#RequestMapping("/files")
#PreAuthorize(value = "hasRole('ROLE_USER')")
public class FileUploadController {
private FileStorageService fileStorageService;
private AuthenticationFacade authenticationFacade;
#Autowired
public FileUploadController(FileStorageService fileUploadService, AuthenticationFacade authenticationFacade) {
this.fileStorageService = fileUploadService;
this.authenticationFacade = authenticationFacade;
}
#ResponseBody
#PostMapping
public ResponseEntity<UUID> uploadFile(#RequestParam("file") MultipartFile file) {
UUID uuid = this.fileStorageService.addFile(authenticationFacade.getAuthentication().getName(), file);
if (uuid != null) return ResponseEntity.ok(uuid);
else return (ResponseEntity<UUID>) ResponseEntity.badRequest();
}
}
Couldn't solve this with #WithMockUser.
You can try using the Profiles approach described here: https://stackoverflow.com/a/35192495/3010484.
I use Spring MVC and Spring boot to write a Restful service. This code works fine through postman.While when I do the unit test for the controller to accept a post request, the mocked myService will always initialize itself instead of return a mocked value defined by when...thenReturn... I use verify(MyService,times(1)).executeRule(any(MyRule.class)); and it shows the mock is not used.
I also tried to use standaloneSetup for mockMoc, but it complains it can't find the mapping for the path "/api/rule".
Could anybody help to figure out the problem?
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class MyControllerTest {
#Mock
private MyService myService;
#InjectMocks
private MyController myRulesController;
private MockMvc mockMvc;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void controllerTest() throws Exception{
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
Long userId=(long)12345;
MyRule happyRule = MyRule.createHappyRule(......);
List<myEvent> mockEvents=new ArrayList<myEvent>();
myEvents.add(new MyEvent(......));
when(myService.executeRule(any(MyRule.class))).thenReturn(mockEvents);
String requestBody = ow.writeValueAsString(happyRule);
MvcResult result = mockMvc.perform(post("/api/rule").contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
verify(MyService,times(1)).executeRule(any(MyRule.class));
String jsonString = result.getResponse().getContentAsString();
}
}
Below is my controller class, where MyService is a interface. And I have implemented this interface.
#RestController
#RequestMapping("/api/rule")
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(method = RequestMethod.POST,consumes = "application/json",produces = "application/json")
public List<MyEvent> eventsForRule(#RequestBody MyRule myRule) {
return myService.executeRule(myRule);
}
}
Is api your context root of the application? If so remove the context root from the request URI and test. Passing the context root will throw a 404. If you intend to pass the context root then please refer the below test case. Hope this helps.
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
#InjectMocks
private MyController myRulesController;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = standaloneSetup(myRulesController).build();
}
#Test
public void controllerTest() throws Exception{
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
MyController.User user = new MyController.User("test-user");
ow.writeValueAsString(user);
MvcResult result = mockMvc.perform(post("/api/rule").contentType(MediaType.APPLICATION_JSON).contextPath("/api")
.content(ow.writeValueAsString(user)))
.andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
}
}
Below is the controller
/**
* Created by schinta6 on 4/26/16.
*/
#RestController
#RequestMapping("/api/rule")
public class MyController {
#RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public User eventsForRule(#RequestBody User payload) {
return new User("Test-user");
}
public static class User {
private String name;
public User(String name){
this.name = name;
}
}
}