I have a method signature like this in controller. when i try to write a unit test for it. it returns 500 instead of 404.
it looks like it is not able to convert the {id} to an Optional
is there any setting I need to do so it can auto convert the parameter to an object?
Thanks
#RequestMapping("/propagationStores")
public class PropagationStoreController {
private StoreRepository storeRepository;
private CustomValidator validator;
public PropagationStoreController(StoreRepository storeRepository) {
this.storeRepository = storeRepository;
}
#GetMapping(value = "/{id}")
public Resource<StoreDto> getById(#PathVariable("id") Optional<Store> storeOptional) {
return storeOptional
.map(StoreConverter::toDto)
.map(store -> {
Resource<StoreDto> resource = new Resource<>(store);
resource.add(new Link("http://localhost").withTitle("localhost"));
return resource;
}).orElseThrow(ResourceNotFoundException::new);
}
when I try to test the getById method using the following code. I am getting 500 instead of 400
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class PropagationStoreControllerIT {
#MockBean
StoreRepository storeRepository;
#MockBean
CustomValidator customValidator;
#Autowired
private MockMvc mockMvc;
#Test
public void testGetById() throws Exception {
when(storeRepository.findById(1l)).thenReturn(Optional.empty());
mockMvc.perform(get("/propagationStores/1")).andDo(print()).andExpect(status().is4xxClientError());
}
}
I was expecting status 404, but I am getting 500.
the log as the following.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /propagationStores/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = local.tux.propagation.controller.PropagationStoreController
Method = public org.springframework.hateoas.Resource<local.tux.propagation.dto.Store$StoreDto> local.tux.propagation.controller.PropagationStoreController.getById(java.util.Optional<local.tux.propagation.evaluator.domain.Store>)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = GET
Request URI = /propagationStores/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = local.tux.propagation.controller.PropagationStoreController
Method = public org.springframework.hateoas.Resource<local.tux.propagation.dto.Store$StoreDto> local.tux.propagation.controller.PropagationStoreController.getById(java.util.Optional<local.tux.propagation.evaluator.domain.Store>)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Range for response status value 500
Expected :CLIENT_ERROR
Actual :SERVER_ERROR
Define your controller method as:
public Resource<StoreDto> getById(#PathVariable("id") Optional<String> id) {
......
}
id can be converted to a string or a number, not into a Store class.
I was able to solve the issue by using adding the #TestConfiguration. It looks like
#MockBean interrupt the normal spring boot initialization, it doesn't register the converter. In order to make it work, we need to register ourself.
#TestConfiguration
static class InternalConfig {
#Bean
WebMvcConfigurer configurer() {
return new WebMvcConfigurer() {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(String.class, Store.class, id -> staticRepository.getOne(Long.parseLong(id)));
}
};
}
}
Related
I use spring boot 2.7 and test with junit
during the execution of the tests I get an error :
the other tests work well.
controller
#PostMapping(value = "/employees")
public ResponseEntity<Employee> addEmployee(#Valid #RequestBody EmployeeDto employeeDto) {
Optional<Employee> employeeDb = employeeService.findByEmail(employeeDto.getEmail());
// must not exist in database
if (!employeeDb.isEmpty()) {
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
// convertion dto -> model
Employee employee = employeeDto.toEmployee();
return new ResponseEntity<>(employeeService.save(employee), HttpStatus.OK);
}
controllerTest
#Test
void addEmployee() throws Exception {
when(employeeService.save(employeeDto.toEmployee())).thenReturn(employee);
when(employeeService.findByEmail(any(String.class))).thenReturn(Optional.of(employee));
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(employeeDto);
mockMvc.perform(
MockMvcRequestBuilders
.post(REST_URL)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8")
.content(json)
)
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.firstname", is(employee.getFirstname())))
.andExpect(jsonPath("$.lastname", is(employee.getLastname())))
.andExpect(jsonPath("$.email", is(employee.getEmail())));
}
the error : "Content type not set"
yet in the test I indicate well the content-type : ".contentType(MediaType.APPLICATION_JSON)"
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/employees/
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"74"]
Body = {"email":"test1#test.com","firstname":"firstname1","lastname":"lastname1"}
Session Attrs = {}
Handler:
Type = com.acme.app1.controllers.EmployeeController
Method = com.acme.app1.controllers.EmployeeController#addEmployee(EmployeeDto)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Content type not set
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:37)
at org.springframework.test.util.AssertionErrors.assertTrue(AssertionErrors.java:70)
at org.springframework.test.util.AssertionErrors.assertNotNull(AssertionErrors.java:106)
for the test to be successful, what should I change?
The content type you've set is used for the request, but in your assertion you're checking for the content type which is returned in the response.
As you can see in the log, there is no content type or body set in the MockHttpServletResponse:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
So there seems to be an issue with the returned object in your mocked result of
when(employeeService.save(employeeDto.toEmployee())).thenReturn(employee);
since the result of this will be rendered to a JSON object you're doing your assertions on.
I'm having an extremely hard time finding consistent resources on how to actually code Junit5 tests for Sprint Boot. Right now, I have have a very simple controller and my attempt at a Junit5 test, but I'm not even able to get the result back from a simple rest call. It's suppose to return an array of string values. I believe the main problem is the MockHttpServletResponse Header isn't appending a port on there, but I'd like it to be dynamically added so whoever pulls down the code can Junit5 test it just fine.
#RestController
public class CnlLetterController {
#Autowired
private CnlLetterService cnlLetterService;
#Autowired
private UserService userService;
#Autowired
private ServiceLetterService serviceLetterService;
#GetMapping("/rest/cnl/templates")
public List<String> findTemplates () {
return cnlLetterService.getTemplates();
}
/* Other methods that don't matter right now */
}
TestController
#ActiveProfiles("dev")
#ExtendWith(SpringExtension.class)
#WebMvcTest(CnlLetterController.class)
class CnlLetterControllerTest {
#MockBean
private ClientRegistrationRepository crr;
#MockBean
private CnlLetterService cnlLetterService;
#MockBean
private UserService userService;
#MockBean
private ServiceLetterService serviceLetterService;
#Autowired
private MockMvc mockMvc;
#Test
void testFindTemplates() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/pis/rest/cnl/templates")
.accept(MediaType.APPLICATION_JSON))
.andDo(print()).andExpect(status().isOk());
}
}
Junit console output
MockHttpServletRequest:
HTTP Method = GET
Request URI = /pis/rest/cnl/templates
Parameters = {}
Headers = [Accept:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [Location:"https://localhost/pis/rest/cnl/templates"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = https://localhost/pis/rest/cnl/templates
Cookies = []
MockHttpServletRequest:
HTTP Method = GET
Request URI = /pis/rest/cnl/templates
Parameters = {}
Headers = [Accept:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [Location:"https://localhost/pis/rest/cnl/templates"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = https://localhost/pis/rest/cnl/templates
Cookies = []
2020-07-20 13:37:29.648 INFO 12868 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
I'm trying to unit test a service for my controller in my API but i'm getting the following error :
2020-05-20 15:23:51.493 WARN 25469 --- [ main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<com.tropicalia.meu_cardapio.domain.user.User> com.tropicalia.meu_cardapio.api.user.update.UserUpdateRest.update(com.tropicalia.meu_cardapio.domain.user.User,java.lang.Long)]
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /users/89
Parameters = {}
Headers = [Content-Type:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.tropicalia.meu_cardapio.api.user.update.UserUpdateRest
Method = com.tropicalia.meu_cardapio.api.user.update.UserUpdateRest#update(User, Long)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
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 = []
java.lang.AssertionError: Status
Expected :202
Actual :400
This is my test class :
#RunWith(SpringRunner.class)
#WebMvcTest(UserUpdateRest.class)
public class UpdateUserTest {
#Autowired
private MockMvc mvc;
#MockBean
private UserUpdateService service;
#Test
public void updateUser_whenPutUser() throws Exception {
User user = new User();
user.setName("Test Name");
user.setId(89L);
given(service.updateUser(user.getId(), user)).willReturn(user);
mvc.perform(put("/users/" + user.getId().toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isAccepted())
.andExpect(jsonPath("name", is(user.getName())));
}
}
And this is my service
#Service
public class UserUpdateService {
#Autowired
UserRepository repository;
public User updateUser(Long id, User user) {
repository
.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found."));
return repository.save(user);
}
}
Would really appreciate if someone could help me with this one.
From what i understand, there's something wrong with the request body but i have no idea what to do to fix it.
As specified in the error message, requestbody is missing.
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing
All you need to do is add body content to the unit test like this
ObjectMapper mapper = new ObjectMapper();
mvc.perform(put("/users/" + user.getId().toString())
.contentType(MediaType.APPLICATION_JSON))
.content(mapper.writeValueAsString(user))
.andExpect(status().isAccepted())
.andExpect(jsonPath("name", is(user.getName())));
you can also pass content like this
.content("{\"id\":\"89\", \"name\":\"Test Name\"}")
I am trying to write a test for an API endpoint but can not get past the security. At least that's what I understand is happening.
From what I understood in the docs (https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/test-method.html) I think I am following the right way?
My Rest controller looks as following. I have an Authentication Principal which I believe is causing the problem.
#RestController
#RequestMapping("/api")
public class VehicleResource {
#GetMapping(value = "/status", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<StatusResponse> getStatus(#AuthenticationPrincipal CustomUser customUser) {
StatusResponse statusResponse = vehicleService.getStatus(customUser.getVin());
LOGGER.debug("Sent statusResponse: {}", statusResponse);
return new ResponseEntity<>(statusResponse, HttpStatus.OK);
}
My test is fairly simple:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = { MobileBackendApplication.class })
#AutoConfigureMockMvc
public class VehicleResourceTest {
#Autowired
private MockMvc mockMvc;
private AccountCredentials testCredentials;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
testCredentials = new AccountCredentials("test.user#test.com", "testPassword");
}
#Test
#WithMockCustomUser
public void getStatusTest() throws Exception {
mockMvc.perform(
get("/api/status").with(httpBasic(testCredentials.getUsername(), testCredentials.getPassword())))
.andExpect(status().isOk());
}
I have a MockCustomUser interface so I can pass along custom users:
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUser {
String username() default "test.user#bmw.com";
String password() default "testPassword";
String vin() default "123TEST456VIN789";
String pushNotificationId() default "testPushId";
}
With the WithMockCustomUserSecurityContextFactory:
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
#Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
CustomUser principal = new CustomUser(customUser.username(), customUser.password(), customUser.vin(),
new ArrayList<String>(Arrays.asList(customUser.pushNotificationId())), null,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(principal, "password",
principal.getAuthorities());
context.setAuthentication(auth);
return context;
}
}
When I run the test I receive a 401 Unauthorized instead of a 200. Other answers referred to maybe missing the Basic Authentication header, but I seem to be passing that along. Below is what is printed when I run the test.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/status
Parameters = {}
Headers = {Authorization=[Basic dGVzdC51c2VyQHNhbXBsZS5jb206dGVzdFBhc3N3b3Jk]}
Body = null
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 401
Error message = Unauthorized
Headers = {Access-Control-Allow-Origin=[*], Access-Control-Allow-Methods=[POST, GET], Access-Control-Max-Age=[3600], Access-Control-Allow-Headers=[Origin, X-Requested-With, Content-Type, Accept, Authorization], Access-Control-Expose-Headers=[Authorization], WWW-Authenticate=[Basic realm="Realm"], X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I'm trying to test my code (Spring-Boot project) of a RestController, but I always get 404.
Here is what I have so far:
#RestController("/service")
public class ServiceInteractionController {
#Autowired
private PairingService pairingService;
#GetMapping("/registered/{sensorId}")
public ResponseEntity isSensorRegistered(#PathVariable String sensorId) {
return ResponseEntity.ok(pairingService.isSensorRegistered(sensorId));
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(ServiceInteractionController.class)
public class ServiceInteractionControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private PairingService pairingService;
#Before
public void setUp() {
Mockito.when(pairingService.isSensorRegistered(TestConstants.TEST_SENSOR_ID))
.thenReturn(true);
}
#Test
public void testIsSensorRegistered() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("service/registered/{sensorId}", TestConstants.TEST_SENSOR_ID))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
The result always looks like this:
MockHttpServletRequest:
HTTP Method = GET
Request URI = service/registered/test123Id
Parameters = {}
Headers = []
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = null
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 = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :404
What am I doing wrong? I already tried to initialize mockmvc directly in setUp method with standaloneSetup() and I have also used #SpringBootTest combined with #AutoConfigureMockMvc.
Does anyone have some useful hints? I use spring boot 2.1.4.
Thanks!
Don't you miss the "/" before the service/registered/{sensorId}?
mockMvc.perform(MockMvcRequestBuilders.get("service/registered/{sensorId}", TestConstants.TEST_SENSOR_ID))
.andExpect(MockMvcResultMatchers.status().isOk());
Try changing this function to read:
#PathVariable
#Test
public void testIsSensorRegistered(#PathVariable sensorId ) throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("service/registered/{sensorId}", TestConstants.TEST_SENSOR_ID))
.andExpect(MockMvcResultMatchers.status().isOk());
}