mock rest api call during integration test in spring boot - spring-boot

I have a spring boot service that validates each request by calling my auth service. Now I am writing an integration test. How can I mock my request to auth service while testing my own APIs?
#GetMapping("/pending/task")
#Operation(summary = "Get user's pending task", tags = "UserTask", security = {#SecurityRequirement(name = Constants.AUTH_TOKEN_HEADER)})
#PreAuthorize(Constants.PreAuthorize.ROLE)
public List<UserTaskDto> getPendingTasks(#Valid #RequestParam long courseId){
// internal logic
}
SpringBoot filter will read the token from the header and verify that against auth service using rest. I want to mock that call during this api testing.
Test Code
class UserTaskControllerTest extends ApplicationTests {
#Mock
RestTemplate restTemplate;
#Test
void shouldGiveAllUserPendingTask(){
HttpHeaders headers = new HttpHeaders();
headers.add(Constants.AUTH_TOKEN_HEADER, GENERIC_AUTH_TOKEN);
Task task = FactoryClass.createTask();
UserTask userTask = FactoryClass.createUserTask();
CentralAuthInfo centralAuthInfo = FactoryClass.getCentralAuthInfo();
taskRepository.save(task);
userTask.setTask(task);
userTaskRepository.save(userTask);
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(USER_PENDING_TASK_BASE_URL)
.queryParam(COURSE_ID, userTask.getCohortId());
when(restTemplate.exchange(ArgumentMatchers.anyString(), ArgumentMatchers.any(HttpMethod.class), ArgumentMatchers.any(HttpEntity.class), ArgumentMatchers.eq(CentralAuthInfo.class))).thenReturn(new ResponseEntity<>(centralAuthInfo, HttpStatus.OK));
ResponseEntity<UserTaskDto> responseEntity = testRestTemplate.exchange(builder.toUriString(), GET, new HttpEntity<>(headers), UserTaskDto.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(200);
}

Spring offers a #WithMockUser that you can add to your tests. I usually use it with a WebTextClient to test API calls. Here is an example with reactive controller but same applies to non-reactive
#Import(SecurityConfig.class)
#WebFluxTest(MyController.class)
class MyControllerTest {
#Autowired
private WebTestClient webTestClient;
...
#Test
#WithMockUser(username="admin",roles={"USER","ADMIN", "ROLE"})
void testPendingTasks() {
webTestClient
.get()
.uri("/pending/task")
.exchange()
.expectStatus()
.isOk();
}
...
}

Maybe #AutoConfigureMockMvc(addFilters = false) will help to disable security for integration test, like this:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc(addFilters = false)
public class FooTest {
#Autowired
private MockMvc mockMvc;
// some of your tests here ....
}
If it's not what you need you can just create a fake auth service class for test purposes and override the behavior in the way you want.

Related

Spring Data Rest cannot do Integration test?

I have tried to use both MockMVC and TestRestTemplate. In both cases, the response back is 404 but the API endpoints work outside of integration test (when I run the spring app on its own).
Does anyone have a working sample app that has a working integration test for a generated controller using Spring Data Rest?
I was also able to write regular integration tests against my own controllers (Non SDR types)
Test code:
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testApi() {
String settings = testRestTemplate
.getForObject("/api/v1/orders", String.class);
System.out.println(settings);
}
}
Repo:
#RepositoryRestResource(excerptProjection = OrderSummaryProjection.class)
public interface OrderRepository extends JpaRepository<Order, Long> {}
Ok I found out the issue but I dont know what the answer should be:
I set spring.data.rest.basePath in application.properties.
But I don't think that file is read when you run the integration tests. How do I fix that?
I currently don't test Spring Data Rest endpoints, but if I were to do it, I would test interfaces using classical Integration test approach:
#RunWith(SpringRunner.class)
#SpringBootTest
public class DummyIT {
#Autowired
private SettingsRepository settingsRepository;
#Test
public void testApi() {
List<Settings> settings = settingsRepository.findAll();
assertNotNull(settings);
}
}
I also tested end-to-end test and it also works, it just returns ugly {"_embedded" : {"settings" : [ { ... } ] }, ... } so it's doable, but it's not pretty:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DummyTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testApi() {
String settings = testRestTemplate
.getForObject("/api/settings", String.class);
System.out.println(settings);
}
}

mock rest template for unit test

I want to mock a RestTemplate in Spring Boot, where I'm making a REST call within a method. To test the controller of the microservice I'm creating,
I want to test methods inside the controller of my micro service.
For example:
#GetMapping(value = "/getMasterDataView", produces = { MediaType.APPLICATION_JSON_VALUE })
#CrossOrigin(origins = { "http://192.1**********" }, maxAge = 3000)
public ResponseEntity<MasterDataViewDTO> getMasterDataView() throws IOException {
final String uri = "http://localhost:8089/*********";
RestTemplate restTemplate = new RestTemplate();
MasterDataViewDTO masterDataViewDTO = restTemplate.getForObject(uri, MasterDataViewDTO.class);
return new ResponseEntity<>(masterDataViewDTO, HttpStatus.OK);
}
how to I test this using mocking?
This is what I have so far:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO masterDataViewDTO= mock(MasterDataViewDTO.class);
//String uri = "http://localhost:8089/*********";
Mockito.when(restTemplate.getForObject(Mockito.anyString(),ArgumentMatchers.any(Class.class))).thenReturn(masterDataViewDTO);
assertEquals("OK",inquiryController.getMasterDataView().getStatusCode());
}
I am getting an error when I'm running the mock, the method getMasterDataView() is getting called and the REST call within it is also getting called and is throwing an error. How can I write my test so that the REST endpoint is not called? If it's possible, I'd like to do this with Mockito.
Before you start writing a test, you should change your code a bit. First of all, it would be a lot easier if you extracted that RestTemplate, and created a separate bean for it which you would inject within your controller.
To do that, add something like this within a #Configuration class or within your main class:
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Additionally, you have to remove the new RestTemplate() from your controller, and autowire it in stead, for example:
#Autowired
private RestTemplate restTemplate;
Now that you've done that, it's going to be a lot easier to inject a mock RestTemplate within your tests.
For your testing, you have two options:
Either mock RestTemplate and all the methods you are trying to access, using a mocking framework (eg. Mockito)
Or you can use MockRestServiceServer, which allows you to write tests that verify if the URLs are properly called, the request matches, and so on.
Testing with Mockito
To mock your RestTemplate with Mockito, you have to make sure that you add the following annotation to your tests:
#RunWith(MockitoJUnitRunner.class)
After that, you can do this:
#InjectMocks
private MyController controller;
#Mock
private RestTemplate restTemplate;
And now you can adjust your tests like this:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO dto = new MasterDataViewDTO();
when(restTemplate.getForObject("http://localhost:8089/*********", MasterDataViewDTO.class)).thenReturn(dto);
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo(dto);
}
You could mock the DTO as you did within your test, but you don't have to, and I don't think there's any benefit from doing so. What you do have to mock is the restTemplate.getForObject(..) call.
Testing with MockRestServiceServer
Another approach is to use MockRestServiceServer. To do that, you have to use the following annotations for your test:
#RunWith(SpringRunner.class)
#RestClientTest
And then you'll have to autowire your controller and MockRestServiceServer, for example:
#Autowired
private MyController controller;
#Autowired
private MockRestServiceServer server;
And now you can write tests like this:
#Test
public void testgetMasterDataView() throws IOException {
server
.expect(once(), requestTo("http://localhost:8089/*********"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(new ClassPathResource("my-mocked-result.json"), MediaType.APPLICATION_JSON));
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// TODO: Write assertions to see if the DTO matches the JSON structure
}
In addition to testing that your actual REST call matches, this also allows you to test if your JSON-to-DTO works as well.
You can achieve this by using #RestClientTest and MockRestServiceServer. An example provided in their documentation:
#RunWith(SpringRunner.class)
#RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest {
#Autowired
private RemoteVehicleDetailsService service;
#Autowired
private MockRestServiceServer server;
#Test
public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
throws Exception {
this.server.expect(requestTo("/greet/details"))
.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
create bean instead of using new RestTemplate() in your services.
use Profile("!test") for your bean that use only for non test profile.
create test class like this:
#SpringBootTest(properties = "spring.profiles.active:test")
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#Log4j2
#Transactional
public class QabzinoMockTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private QabzinoLogRepository qabzinoLogRepository;
private MockMvc mockMvc;
private final WebServiceStatus successStatus = new WebServiceStatus();
#Mock
private RestTemplate restTemplate;
private final Gson mapper = new Gson();
#TestConfiguration
static class Config {
#Bean
public RestTemplate rest() {
return Mockito.mock(RestTemplate.class);
}
}
#Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
this.restTemplate = (RestTemplate) this.webApplicationContext.getBean("rest");
LoginOutput responseModel = new LoginOutput();
responseModel.setStatus("200");
Mockito.when(
restTemplate.postForEntity(
Mockito.eq(LOGIN_URL),
Mockito.isA(HttpEntity.class),
Mockito.eq(String.class)
)
).thenReturn(ResponseEntity.ok().body(mapper.toJson(responseModel)));
}
}
in this sample we create bean with rest name in static class Config and mock it in setup method Before all tests.
and preferred test config maybe useful for you application-test.properties:
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE;IGNORECASE=TRUE;
spring.datasource.username=sa
spring.datasource.password=sa
and finally when we see restTemplate in our code, mock bean return our responseModel instead of real service call :)

Testing Spring Data Rest

I want to test a Spring boot 2 respository as rest controller app.
App is working well from browser ( http://localhost:8080/api/v1/ehdata ), but I cannot find an example how can I test it with Spring test environment. Very important, there are no RestControllers and Services, only Repositories annotated like this:
#RepositoryRestResource(path = EhDataRepository.BASE_PATH,
collectionResourceRel = EhDataRepository.BASE_PATH)
public interface EhDataRepository extends
PagingAndSortingRepository<EhData, Long> {
public static final String BASE_PATH="ehdata";
}
I tried with this test, but responses was empty, and status code was 404:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebMvcTest(EhDataRepository.class)
public class RestTest extends AbstractRestTest {
#Autowired MockMvc mvc;
#Test
public void testData() throws Exception {
mvc.perform(get("/api/v1/ehdata")
.accept(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE,
MediaTypes.HAL_JSON_VALUE+";charset=UTF-8")
.andReturn();
}
}
thx,
Zamek
You will need to mock the output from the respository like this based on the method you are trying to test:
#MockBean
private ProductRepo repo;
And then
Mockito.when(this.repo.findById("PR-123")
.get())
.thenReturn(this.product);
this.mvc.perform(MockMvcRequestBuilders.get("/products/{id}", "PR-123")
.contentType(MediaType.APPLICATION_JSON_VALUE))
.andReturn();
Also, remove the server-context-path while calling API in perform() method.

#WebMvcTest for SOAP?

Is the Spring Boot annotation #WebMvcTest only intended for sliced RestController tests or should SOAP Endpoints be testable with it too?
When I setup my test and run it, I only get a 404 response as if the endpoint wasn't there so I assume it isn't part of the WebMvc slice.
#RunWith(SpringRunner.class)
#WebMvcTest(value = IdServerPortTypeV10.class)
#Import({SecurityConfig.class, ModelMapperConfig.class, WebServiceConfig.class, ControllerTestBeans.class})
public class AccountEndpointTests {
#Autowired
IdServerPortTypeV10 soapEndpoint;
...
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity())
.build();
}
#Test
#WithMockUser(roles = VALID_ROLE)
public void getAccountTest_Success() throws Exception {
mockMvc.perform(
post("/soap/idserver/1.0")
.accept(MediaType.TEXT_XML_VALUE)
.headers(SoapTestUtility.getHeader(SERVICE.getNamespaceURI(), "getAccount"))
.content(SoapTestUtility.getAccountXml())
).andDo(print())
.andExpect(status().isOk());
}
}
The endpoint is enabled in WebServiceConfig.class in which #EnableWs is set.
#WebMvcTest is, as the name implies, only for Spring MVC related tests.
Spring's SOAP support is from the Spring Web Services project which has its own integration testing support.

Mock SecurityContextHolder / Authentication always returning null

I'm aware this question gets asked a lot, but maybe I have some things that are particular to this. I'm trying to do some integration tests on a Spring Boot application that supports REST (not Spring MVC) and for some reason SecurityContextHolder.getContext().getAuthentication() always returns null, even when using #WithMockUser on the test. I'm not certain if this has to do with using profiles on the configuration classes, but so far we haven't had troubles with this.
Class
#Override
public ResponseEntity<EmployeeDTO> meGet() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
logger.debug("Endpoint called: me({})", principal);
EmployeeDTO result;
// Get user email from security context
String email = principal.getName(); // NPE here
// ...
}
Test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"eureka.client.enabled:false"})
#WithMockUser
#ActiveProfiles(value = "test")
public class MeControllerTest extends IntegrationSpringBootTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private SecurityContext securityContext;
#MockBean
private Authentication authentication;
#MockBean
private EmployeeRepository employeeRepository;
#BeforeClass
public static void setUp() {
}
#Before
#Override
public void resetMocks() {
reset(employeeRepository);
}
#Test
public void meGet() throws Exception {
when(securityContext.getAuthentication()).thenReturn(authentication);
securityContext.setAuthentication(authentication);
when(authentication.getPrincipal()).thenReturn(mockEmployee());
SecurityContextHolder.setContext(securityContext);
when(employeeRepository.findByEmail(anyString())).thenReturn(mockEmployee());
ResponseEntity<EmployeeDTO> employeeDTOResponseEntity =
this.restTemplate.getForEntity("/me", EmployeeDTO.class);
// ...
}
If I return a mock Principal instead of mockEmployee() the test cannot even start because this happens:
org.springframework.beans.factory.BeanCreationException: Could not inject field: private org.springframework.security.core.Authentication com.gft.employee.controller.MeControllerTest.authentication; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'org.springframework.security.core.Authentication#0' is expected to be of type 'org.springframework.security.core.Authentication' but was actually of type '$java.security.Principal$$EnhancerByMockitoWithCGLIB$$657040e6'
Additional clarifications: This Spring Boot app also uses OAuth2 for authorization, but it must be turned off for these tests. That's why we use profiles. Omitting the #ActiveProfiles annotation gives us a 401 Unauthorized error against the endpoint request.
I could use PowerMock but I would like to avoid it if possible.
Easier Way of writing Junit for Authentication SecurityContextHolder would be to mock them. Following is the working implementation of it.
You can add the mock classes as per your need and then set context of SecurityContextHolder and then use when() to further mock and return proper mock value.
AccessToken mockAccessToken = mock(AccessToken.class);
Authentication authentication = mock(Authentication.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
when(SecurityContextHolder.getContext().getAuthentication().getDetails()).thenReturn(mockSimpleUserObject);
I ended up using MockMvc despite the app not being Spring MVC-based. Additionally, I separated the SecurityContext calls into another service, but before doing that I could assert that the #WithMockUser annotation was working properly.
What's key for this to work is using these snippets at class level:
#WebMvcTest(MeController.class)
#Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
Using #WebMvcTest facilitates not having to initialize a SecurityContext in the first place. You don't even have to call springSecurity(). You can just just the mockMvc.perform() operations as usual, and any calls to the SecurityContext will return whatever mocked user you specify, either with #WithMockUser or mocking the service that handles such a call.
This sample code is working for me. This code is using JUnit 5.
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc //need this in Spring Boot test
public class LoginControllerIntegrationTest {
// mockMvc is not #Autowired because I am customizing it #BeforeEach
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Mock
DefaultOidcUser principal;
#BeforeEach
public void beforeEach() {
Authentication authentication = mock(OAuth2AuthenticationToken.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
when(authentication.getPrincipal()).thenReturn(principal);
SecurityContextHolder.setContext(securityContext);
// setting mockMvc with custom securityContext
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void given_any_OAuth2AuthenticationToken_when_login_then_redirect_to_logout() throws Exception {
final String loginName = "admin";
// given
// manipulate the principal as needed
when(principal.getAttribute("unique_name")).thenReturn(loginName);
// #formatter:off
// when
this.mockMvc.perform(get("/login"))
.andDo(print())
//then
.andExpect(status().isFound())
.andExpect(redirectedUrl("/logout"));
// #formatter:off
}
}

Resources