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 = []
Related
I have a spring boot application with rest controller that has to accept binary stream at post endpoint and do the things with it.
So i have:
#PostMapping(path="/parse", consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public String parse(RequestEntity<InputStream> entity) {
return service.parse(entity.getBody());
}
When i try to test it with MockMvc i get org.springframework.web.HttpMediaTypeNotSupportedException.
I see in log:
Request:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /parse
Parameters = {}
Headers = [Content-Type:"application/octet-stream;charset=UTF-8", Content-Length:"2449"]
Body = ...skipped unreadable binary data...
Session Attrs = {}
Response:
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Accept:"application/json, application/*+json", 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 tried to add explicit header:
#PostMapping(path="/parse", consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE},
headers = "Accept=application/octet-stream")
Does not help.
The testing call is:
mvc.perform(post("/parse")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.content(bytes)
).andDo(print())
.andExpect(status().isOk());
How can i make it work without going to multipart-form?
I have analysed this issue and got to know that issue is in this method.
#PostMapping(path="/parse", consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public String parse(RequestEntity<InputStream> entity) {
return service.parse(entity.getBody());
}
Here method parameter is of type RequestEntity<InputStream> it should be HttpServletRequest
So here is the fix.
#PostMapping(value = "/upload",
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public String demo(HttpServletRequest httpServletRequest) {
ServletInputStream inputStream;
try {
inputStream = httpServletRequest.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
final List<String> list = new BufferedReader(new InputStreamReader(inputStream))
.lines().toList();
System.out.println(list);
return "Hello World";
}
Test Case
#Autowired
private MockMvc mockMvc;
#Test
public void shouldTestBinaryFileUpload() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders
.post("/upload")
.content("Hello".getBytes())
.contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(MockMvcResultMatchers
.status()
.isOk())
.andExpect(MockMvcResultMatchers
.content()
.bytes("Hello World".getBytes()));
}
I am trying to make intergration tests for my webflux controller, but the test are failing either on not set content-type or on empty content.
Controller class:
#RestController
#RequiredArgsConstructor
#SecurityRequirement(name = "bearerAuth")
#Log4j2
public class OnboardingController {
private final Service service;
#GetMapping(value = "/organizationQuotas", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<OrganizationQuota> getOrganizationQuotas() {
return service.getAllOrganizationQuotas();
}
}
Service class is a simple flux-returning service.
Test class:
#WebMvcTest(OnboardingController.class)
#RunWith(SpringRunner.class)
public class OnboardingControllerIT {
#MockBean
Service service;
private EasyRandom easyRandom = new EasyRandom();
#Autowired
private MockMvc mockMvc;
#Test
#WithMockUser(authorities = {"..."})
#DisplayName("Should List All organization quotas when GET request to /organizationQuotas")
public void shouldReturnOrganizationQuotas() throws Exception {
when(service.getAllOrganizationQuotas())
.thenReturn(Flux.fromStream(easyRandom.objects(OrganizationQuota.class, 5)));
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/organizationQuotas").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
// .andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.*", isA(ArrayList.class)))
.andReturn();
}
}
At this state the output looks like this:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /organizationQuotas
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json"]
Body = null
Session Attrs = {SPRING_SECURITY_CONTEXT=org.springframework.security.core.context.SecurityContextImpl#d1e9b4bb: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#d1e9b4bb:...}
Handler:
Type = controller.OnboardingController
Method = controller.OnboardingController#getOrganizationQuotas()
Async:
Async started = true
Async result = [OrganizationQuota{allowPaidServicePlans=true, ...}]
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", 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 = []
and it ends with exception
No value at JSON path "$.*"
java.lang.AssertionError: No value at JSON path "$.*"
...
I have these dependencies
testImplementation 'org.jeasy:easy-random-randomizers:5.0.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testCompile 'io.projectreactor:reactor-test'
Security should work ok. I can see the async result that is correct but my matchers are not working with it.
The content type is not returned as well. Is it because of the async character of request? How can I make it evaluate? Thanks for help.
I found it at howToDoInJava that testing async controller is different:
I had to use asyncDispatch method. Form the referenced page, here is the example:
#Test
public void testHelloWorldController() throws Exception
{
MvcResult mvcResult = mockMvc.perform(get("/testCompletableFuture"))
.andExpect(request().asyncStarted())
.andDo(MockMvcResultHandlers.log())
.andReturn();
mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith("text/plain"))
.andExpect(content().string("Hello World !!"));
}
now it works correctly.
I have the following test code where I'm testing a Pageable endpoint that list all entries for student.
#Autowired
private MockMvc mockMvc;
#MockBean
private StudentRepository studentRepository;
private PageableHandlerMethodArgumentResolver pageableArgumentResolver = new PageableHandlerMethodArgumentResolver();
#BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(new StudentEndpoint(studentRepository))
.setCustomArgumentResolvers(pageableArgumentResolver)
.build();
}
#Test
#WithMockUser(username = "xx", password = "xx", roles = "USER")
public void whenListStudentUsingCorrectStudentUsernameAndPassword_thenReturnStatusCode200 () throws Exception {
List<Student> students = asList(new Student(1L, "Legolas", "legolas#lotr.com"),
new Student(2L, "Aragorn", "aragorn#lotr.com"));
when(studentRepository.findAll()).thenReturn(students);
mockMvc.perform(get("http://localhost:8080/v1/protected/students/"))
.andExpect(status().isOk())
.andDo(print());
verify(studentRepository, times(1)).findAll();
}
The problem here is that the verify(studentRepository, times(1)).findAll(); doesn't work because MockHttpServletResponse is returning a null Body.
Thats my endpoint:
#GetMapping(path = "protected/students")
public ResponseEntity<?> listAll (Pageable pageable) {
return new ResponseEntity<>(studentDAO.findAll(pageable), HttpStatus.OK);
}
And my log:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null Redirected URL = null
Cookies = []
Argument(s) are different! Wanted:
br.com.devdojo.repository.StudentRepository#0 bean.findAll(
);
-> at br.com.devdojo.TestingTestTech.whenListStudentUsingCorrectStudentUsernameAndPassword_thenReturnStatusCode200(TestingTestTech.java:68)
Actual invocations have different arguments:
br.com.devdojo.repository.StudentRepository#0 bean.findAll(
Page request [number: 0, size 20, sort: UNSORTED]
);
Could someone please help with the right way to test pageable response? Thanks.
Finally, I found how to fix it.
You just need to pass a Pageable object as parameter to your findAll method that returns a Pageable JSON.
Thats my new working code:
Page<Student> pagedStudents = new PageImpl(students);
when(studentRepository.findAll(isA(Pageable.class))).thenReturn(pagedStudents);
mockMvc.perform(get("http://localhost:8080/v1/protected/students/"))
.andExpect(status().isOk())
.andDo(print());
verify(studentRepository).findAll(isA(Pageable.class));
And the MockHttpServletResponse:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json"]
Content type = application/json
Body = {"content":[{"id":1,"name":"Legolas","email":"legolas#lotr.com"},{"id":2,"name":"Aragorn","email":"aragorn#lotr.com"}],"pageable":"INSTANCE","totalElements":2,"totalPages":1,"last":true,"size":2,"number":0,"sort":{"sorted":false,"unsorted":true,"empty":true},"first":true,"numberOfElements":2,"empty":false}
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());
}
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)));
}
};
}
}