i have endpoint like this:
#PostMapping("/departure")
public ResponseEntity<String> departure(#RequestBody CarAtGateModel carAtGateModel) throws UnidentifiedCarException {
CarAndParkingEntity carAndParkingEntity = carsAndParkingsRepository.findByIdCar(
carAtGateModel.getCarEntity().getIdCar()).orElseThrow(() -> new UnidentifiedCarException());
carAndParkingEntity.setIdParking("-1");
carsAndParkingsRepository.flush();
return new ResponseEntity<>(responsesMessages.gateUp(), HttpStatus.OK);
}
and next i wanted to do some tests with mocks like:
#Test
public void departureWorksWhenCarOnDepartureIsRecognized() {
//given
carsAndParkingsRepository = mock(CarsAndParkingsRepository.class);
CarAndParkingEntity carAndParkingEntity = new CarAndParkingEntity();
CarAtGateModel carAtGateModel = new CarAtGateModel();
CarEntity carEntity = new CarEntity();
carEntity.setIdCar("-1");
carEntity.setProducer("a");
carEntity.setModel("b");
carEntity.setWidth(1.6);
carEntity.setPowerType(PowerType.PB);
carAtGateModel.setCarEntity(carEntity);
carAtGateModel.setParkingId("parkingId");
//when
when(carsAndParkingsRepository.findByIdCar(carEntity.getIdCar())).thenReturn(Optional.of(carAndParkingEntity));
HttpEntity<CarAtGateModel> request = new HttpEntity<>(carAtGateModel);
ResponseEntity<String> response = testRestTemplate.postForEntity("/departure", request, String.class);
//then
assertEquals(200, response.getStatusCodeValue());
}
Mocking repository for return optional works correctly, it returns carAndParkingEntity.
But test doesn't pass becouse it throws UnidentifiedCarException and I can't understand why I'm getting exception when carsAndParkingsRepository.findByIdCar(carEntity.getIdCar()) returns optional, so it should exist...
You are providing lack of code to indentify problem correctly. From what i see, your mocked repository is nowhere injected into the controller. So you can not expect repository in controller to behave in a way you declared in your test.
Here is an example of how to test web layer in Spring https://spring.io/guides/gs/testing-web/
Tip: you shouldn't use repository nor business logic in your web layer. It is just terrible practice. Separate all logic into separate layer and mock that instead. Then you can make separate unit tests just for your logic. Testing your web layer shouldn't include testing of your repository access or business logic
Related
I'm working at a small parking service REST API.
For example I have endpoint:
#RequestMapping("/departure")
public ResponseEntity<String> departure(#RequestBody CarAtGateModel carAtGateModel) throws Exception {
return parkingService.carDeparture(carAtGateModel.getCarEntity().getIdCar());
}
method parkingService.carDeparture looks like this:
public ResponseEntity<String> carDeparture(String carID) throws UnidentifiedCarException {
CarAndParkingIDsEntity carAndParkingIDsEntity = carAndParkingIDsRepository.findByIdCar(carID);
if (carAndParkingIDsEntity == null) {
throw new UnidentifiedCarException();
} else {
carAndParkingIDsEntity.setIdParking("-1");
carAndParkingIDsRepository.flush();
return new ResponseEntity<>("Gate up", HttpStatus.OK);
}
}
And the problem is when I'm trying to do some integration tests. Unit tests for parking service pass correctly but I don't know exactly what should I do for integration tests.
I was thinking about something like. Mock carAtGateModel (it's carId and ParkingId) and send it to the endpoint and then mock parkingservice because I'm using it inside and I don't want to change data in the database.
when(parkingService.carDeparture(anyString())).thenReturn(new ResponseEntity<>("Gate up",
HttpStatus.OK));
HttpEntity<CarAtGateModel> request = new HttpEntity<>(carAtGateModel);
ResponseEntity<String> response = testRestTemplate.postForEntity("/departure", request,
String.class);
I would suggest you use #WebMvcTest instead to have a slice test (sort of integration test that focuses on a single application layer).
#ExtendWith(SpringExtension.class)
#WebMvcTest(YourController.class)
public class YourControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private ParkingService parkingService;
// Your test cases here
}
You would use mockMvc to perform an actual call to the REST endpoint with an actual CarAtGateModel object as JSON in the request body. You would also mock the behaviour of your ParkingService, thus focusing the test on your Controller.
i have something like that in my ParkingServiceController.
#PostMapping("/departure")
public ResponseEntity<String> departure(#RequestBody CarAtGateModel carAtGateModel) throws UnidentifiedCarException {
CarAndParkingEntity carAndParkingEntity = carsAndParkingsRepository.findByIdCar(
carAtGateModel.getCarEntity().getIdCar()).orElseThrow(() -> new UnidentifiedCarException());
carAndParkingEntity.setIdParking("-1");
carsAndParkingsRepository.flush();
return new ResponseEntity<>(responsesMessages.gateUp(), HttpStatus.OK);
}
and next i wanted to do test with some mocks.
#Test
public void testArrivalWhenParkingIdNotExists() {
//given
CarAndParkingEntity carAndParkingEntity = mock(CarAndParkingEntity.class);
carAtGateModel = mock(CarAtGateModel.class);
//when
when(carsAndParkingsRepository.findByIdCar(anyString())).thenReturn(Optional.of(carAndParkingEntity));
HttpEntity<CarAtGateModel> request = new HttpEntity<>(carAtGateModel);
ResponseEntity response = testRestTemplate.postForEntity("/departure", request, String.class);
//then
assertEquals("Parking with that id does not exists", response.getBody());
}
but i'm getting that exception every time with every code change in test
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.example.parkingservice.models.CarAtGateModel$MockitoMock$1316802841["mockitoInterceptor"]->org.mockito.internal.creation.bytebuddy.MockMethodInterceptor["serializationSupport"])
When i'm not using any mock test passess correctly so imo i'm doing something wrong with mocking
At first sight, as your ParkingServiceController has a composition with CarsAndParkingRepository, you should mock that dependency first. I don't know why you are mocking carAtGateModel, you can use a real object representing the the data you want to pass to the controller (the same applies to carAndParkingEntity).
It would be helpful if you add more details, explaining what you want to test because it's not totally clear the assertion you are doing.
#Test
public void getEventsByOrg() throws Exception {
String mockResposne = getXMLFromFile("classpath:OrgResponse.xml");
ResponseEntity<String> response = new ResponseEntity<>(mockResposne, HttpStatus.OK);
when(restTemplate.exchange(any(String.class), any(), any(HttpEntity.class), any(Class.class)))
.thenReturn(response);
ResponseEntity<List<OCVEvents>> ocvEvents = eventService.getEventsByGlobalKey(eventIdOrg, traceId);
verify(restTemplate).exchange(any(String.class), any(), captor.capture(), any(Class.class));
Events event = ocvEvents.getBody().get(0);
Events eventsPerson = new ObjectMapper().readValue(ResourceUtils.getFile("classpath:EventOrg.json"), Events.class);
assertThat(event.getHeader()).isEqualTo(eventsPerson.getHeader());
Now i have another Rest Call inside eventsService
How to write unit test for that
I need to hit a Rest API(1) & get the response, based on response i need to hit another Rest API (2)
i need to write the mockit0 for this class
If you have multiple rest api calls being made in your service class , you will have to mock all the api calls to return a mock data for your test to run. Just like you mocked the response for the first api call, add a mock response for the second api call before calling your service in you test. Instead of using any() as the argument matcher specify the particular url that you are going to be calling from the code to differentiate between the two api call mocks.
If you are on Springboot and using spring-boot-test for Integration testing , then you could use TestRestTemplate like:
TestRestTemplate testRestTemplate = new TestRestTemplate();
ResponseEntity<String> response = testRestTemplate.
getForEntity(FOO_RESOURCE_URL + "/1", String.class);
assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
or If you are just having a Unit test case using Mockito for instance then :
#Mock
private RestTemplate restTemplate;
#InjectMocks
private EventService eventService = new EventService();
#Test
public void givenMockingIsDoneByMockito_whenGetIsCalled_shouldReturnMockedObject() {
SomeObject instance = new SomeObject(“E001”, "Eric Simmons");
Mockito
.when(restTemplate.getForEntity(
“http://localhost:8080/test/E001”, SomeObject.class))
.thenReturn(new ResponseEntity(instance, HttpStatus.OK));
SomeObject returnedObject = eventService.getEventsByGlobalKey(id);
Assert.assertEquals(instance, returnedObject);
}
I am quite new to the Spring ecosystem in general and Webflux. There are 2 things that I am trying to figure out and cannot find any specifics about.
My Setup:
I am writing a Spring Boot 2 REST API using WebFlux (not using controllers but rather handler functions). The authentication server is a separate service which issues JWT tokens and those get attached to each request as Authentication headers. Here is a simple example of a request method:
public Mono<ServerResponse> all(ServerRequest serverRequest) {
return principal(serverRequest).flatMap(principal ->
ReactiveResponses.listResponse(this.projectService.all(principal)));
}
Which i use to react to a GET request for a list of all "Projects" that a user has access to.
I afterwards have a service which retrieves the list of projects for this user and i render a json response.
The Problems:
Now in order to filter the projects based on the current user id i need to read it from the request principal. One issue here is that i have plenty service methods which need the current user information and passing it through to the service seems like an overkill. One solution is to read the principal inside the service from:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Question 1:
Is this a good practice in general when writing functional code (If i do this instead of propagating the principal)? is this a good approach despite the complexity of reading and sending the principal from the request to the service in each method?
Question 2:
Should i instead use the SecurityContextHolder Thread Local to fetch the principal, and if i do that how do i write tests for my service?
If i use the Security Context how do i test my service implementations which are expecting a principal that is of type JWTAuthenticationToken
and i always get null when trying to do something like described here: Unit testing with Spring Security
In the service tests, In tests what i've managed to do so far is to propagate the principal to the service methods and use mockito to mock the principal. This is quite straightforward.
In the Endpoint Tests i am using #WithMockUser to populate the principal when doing requests and i mock out the service layer. This has the downside of the principal type being different.
Here is how my test class for the service layer looks:
#DataMongoTest
#Import({ProjectServiceImpl.class})
class ProjectServiceImplTest extends BaseServiceTest {
#Autowired
ProjectServiceImpl projectService;
#Autowired
ProjectRepository projectRepository;
#Mock
Principal principal;
#Mock
Principal principal2;
#BeforeEach
void setUp() {
initMocks(this);
when(principal.getName()).thenReturn("uuid");
when(principal2.getName()).thenReturn("uuid2");
}
// Cleaned for brevity
#Test
public void all_returnsOnlyOwnedProjects() {
Flux<Project> saved = projectRepository.saveAll(
Flux.just(
new Project(null, "First", "uuid"),
new Project(null, "Second", "uuid2"),
new Project(null, "Third", "uuid3")
)
);
Flux<Project> all = projectService.all(principal2);
Flux<Project> composite = saved.thenMany(all);
StepVerifier
.create(composite)
.consumeNextWith(project -> {
assertThat(project.getOwnerUserId()).isEqualTo("uuid2");
})
.verifyComplete();
}
}
Based on the other answer, i managed to solve this problem in the following way.
I added the following methods to read the id from claims where it normally resides within the JWT token.
public static Mono<String> currentUserId() {
return jwt().map(jwt -> jwt.getClaimAsString(USER_ID_CLAIM_NAME));
}
public static Mono<Jwt> jwt() {
return ReactiveSecurityContextHolder.getContext()
.map(context -> context.getAuthentication().getPrincipal())
.cast(Jwt.class);
}
Then i use this within my services wherever needed, and i am not forwarding it through the handler to the service.
The tricky part was always testing. I am able to resolve this using the custom SecurityContextFactory. I created an annotation which i can attach the same way as #WithMockUser, but with some of the claim details i need instead.
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockTokenSecurityContextFactory.class)
public #interface WithMockToken {
String sub() default "uuid";
String email() default "test#test.com";
String name() default "Test User";
}
Then the Factory:
String token = "....ANY_JWT_TOKEN_GOES_HERE";
#Override
public SecurityContext createSecurityContext(WithMockToken tokenAnnotation) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
HashMap<String, Object> headers = new HashMap<>();
headers.put("kid", "SOME_ID");
headers.put("typ", "JWT");
headers.put("alg", "RS256");
HashMap<String, Object> claims = new HashMap<>();
claims.put("sub", tokenAnnotation.sub());
claims.put("aud", new ArrayList<>() {{
add("SOME_ID_HERE");
}});
claims.put("updated_at", "2019-06-24T12:16:17.384Z");
claims.put("nickname", tokenAnnotation.email().substring(0, tokenAnnotation.email().indexOf("#")));
claims.put("name", tokenAnnotation.name());
claims.put("exp", new Date());
claims.put("iat", new Date());
claims.put("email", tokenAnnotation.email());
Jwt jwt = new Jwt(token, Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS), headers,
claims);
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, AuthorityUtils.NO_AUTHORITIES); // Authorities are needed to pass authentication in the Integration tests
context.setAuthentication(jwtAuthenticationToken);
return context;
}
Then a simple test will look like this:
#Test
#WithMockToken(sub = "uuid2")
public void delete_whenNotOwner() {
Mono<Void> deleted = this.projectService.create(projectDTO)
.flatMap(saved -> this.projectService.delete(saved.getId()));
StepVerifier
.create(deleted)
.verifyError(ProjectDeleteNotAllowedException.class);
}
As you are using Webflux you should be using the ReactiveSecurityContextHolder to retrieve the principal like so : Object principal = ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal();
The use of the non-reactive one will return null as you are seeing.
There is more info related to the topic in this answer - https://stackoverflow.com/a/51350355/197342
How to write a POST method test case if the return type of a particular create method in the service layer is ResponseEntity<Object>?
This is my createOffer method:
public ResponseEntity<Object> createOffer(Offer offer) {
Offer uoffer = offerRepository.save(offer);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{jobTitle}").
buildAndExpand(uoffer.getJobTitle()).toUri();
return ResponseEntity.created(location).build();
}
and this is its corresponding test class method:
#Test
public void testCreateOffer() {
Offer offer = new Offer("SE",new Date(),5);
Mockito.when( offerRepository.save(offer)).thenReturn( offer);
assertThat(offerServiceImpl.createOffer(offer)).isEqualTo(offer);
}
Here I am getting an error while running this test case which is no current servlet request attributes and exception is:
java.lang.IllegalStateException
Why is it coming
This answers the above question.
Hope it helps when someone finds the same issue !!!
#Test
public void testCreateOffer() {
Offer offer = new Offer("SE",new Date(),5);
MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{jobTitle}").
buildAndExpand(offer.getJobTitle()).toUri();
ResponseEntity<Object> response = ResponseEntity.created(location).build();
Mockito.when( offerRepository.save(offer)).thenReturn(offer);
assertThat( offerServiceImpl.createOffer(offer)).isEqualTo(response);
}
Problem is that in your method you want to get infromation from class ServletUriComponentsBuilder. When you open this class in comment is
UriComponentsBuilder with additional static factory methods to create
links based on the current HttpServletRequest.
So it means when your application is running on server (e.g. tomcat) you have context and you can read information from HttpServletRequest. But in junit you don't have context and you can't get this iformation. So when your code is runnig and reach the ServletUriComponentsBuilder.fromCurrentRequest() then the code is done. So you have to mock it. Look at this link it can help you.
ServletUriComponentsBuilderTests
Kotlin.
I was getting java.lang.IllegalStateException: No current ServletRequestAttributes cause I had this line in my service:
val location = ServletUriComponentsBuilder.fromCurrentRequest().build().toUri()
I have put the following into my setUp() function:
#BeforeEach
fun setup() {
MockitoAnnotations.openMocks(this)
val request = MockHttpServletRequest()
RequestContextHolder.setRequestAttributes(ServletRequestAttributes(request))
}