Unable to mock component that uses RestTemplateBuilder - spring-boot

I have a Springboot application that uses RestTemplate to access a third-party json api.
I have written a Component that has the RestTemplate field which is instantiated using Constructor Injection.
#Service("sample")
public class SampleService {
private RestTemplate restTemplate;
#Autowired
public SampleService(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
The service works fine.
I am trying to write test cases for the same.
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest(classes = {Application.class,ServiceTest.ContextConfiguration.class})
public class ServiceTest {
#InjectMocks
#Qualifier("sample")
SampleService sampleService;
#Configuration
static class ContextConfiguration {
#Bean
public RestTemplateBuilder restTemplateBuilder() {
RestTemplateBuilder rtb = mock(RestTemplateBuilder.class);
RestTemplate restTemplate = mock(RestTemplate.class);
when(rtb.build()).thenReturn(restTemplate);
return rtb;
}
}
}
I am getting the below exception on test class startup.
org.mockito.exceptions.misusing.InjectMocksException:
Cannot instantiate #InjectMocks field named 'sampleService' of type 'class com.xxx.xxx.core.SampleService'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : Cannot invoke "org.springframework.boot.web.client.RestTemplateBuilder.build()" because "builder" is null
How to resolve this?

Related

wrap a spy in a springBoot application class throws java.lang.IllegalStateException: Failed to unwrap proxied object

i'am wrapping a service in a spy , that i put in springBoot application class annotated with #SpringBootApplication,
when i'am injecting the service in a test class it does not work, it injects the real service and not the spy , the test fails and i have the following exception : java.lang.IllegalStateException: Failed to unwrap proxied object.
here is my code :
#SpringBootApplication
public class ApiApplication {
#Bean
#Primary
public ApiService myservice(ApiService actual) {
return Mockito.spy(actual);
}
and i inject it in test :
public class ApiStepDefs {
private final MockMvc mockMvc;
private final ApiService apiServiceCustom;
public ApiStepDefs(MockMvc mockMvc, ApiService apiServiceCustom) {
this.mockMvc = mockMvc;
this.apiServiceCustom = apiServiceCustom;
}
thanks

Multiple rest templates in spring boot

I want to configure multiple rest template clients to access different API's. Both are having different authorization headers. I already configured one, Same way configured other rest template too, but that throws error bean 'restTemplate' defined in class path resource .class could not be registered..
#Configuration
public class RestTemplateConfig {
#Autowired
private HeaderRequestInterceptor headerRequestInterceptor;
//constructor
public RestClientConfig() {}
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(headerRequestInterceptor));
return restTemplate;
}
}
HeaderRequestInterceptor has base64 encoded authorization, so could not post that code here.
Another RestTemplate:
#Configuration
public class AnotherRestClientConfig {
#Autowired
private AnotherHeaderRequestInterceptor anotherHeaderRequestInterceptor;
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(anotherHeaderRequestInterceptor));
return restTemplate;
}
}
Could someone let me know how to configure multiple rest templates in an application.
you could use #Qualifier as mentioned by #VirtualTroll. Or create a specific client bean per api and hold the restemplate instance there.
#Component
public class ApiClient1 {
private final RestTemplate customRestTemplate;
public ApiClient1() {
this.customRestTemplate = ...
}
public void useApi() {
}
}

Could not autowire. No beans of 'InstructionRepository' type found

Trying to create a bean in SpringBoot application, but getting the following error "Could not autowire. No beans of 'InstructionRepository' type found."
InstructionRepository is annotated with #Repository annotation in the jar and is an Interface extending a Spring Data Interface
ScheduleProcessor is a method
When I Try adding the #ComponentScan annotation by passing the base package value, the error goes away BUT, when I boot up the application get the following error
Parameter 0 of constructor in com.xxx.resync.config.AppConfig required a bean of type 'com.xxx.repo.InstructionRepository' that could not be found. Action: Consider defining a bean of type 'com.xxx.repo.InstructionRepository' in your configuration.
#Configuration
#EnableAutoConfiguration
//#ComponentScan(basePackages = {"com.xxx.repo"})
public class AppConfig {
#Value("${pssHttp.connectTimeout:3000}")
private int connectTimeout;
#Bean
public RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(connectTimeout);
factory.setReadTimeout(connectTimeout);
restTemplate.setRequestFactory(factory);
return restTemplate;
}
#Bean
public ScheduleUpdater getScheduleUpdater() {
return new ScheduleUpdater(true);
}
#Bean
public ScheduleProcessor scheduleProcessor(InstructionRepository instructionRepository, ScheduleUpdater scheduleUpdater) {
return new ScheduleProcessor(instructionRepository, scheduleUpdater);
}
}
InstructionRepository
#Repository
public interface InstructionRepository extends CouchbaseRepository<Instruction, String> {
}
How can we fix the error and be able to boot up the Spring boot application?
Any suggestions appreciated.
You need to add #EnableCouchbaseRepositories to enable repo building eg to AppConfig.

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 :)

Spring boot test: MockedBean conflicts with others

Currently, I've two RestTemplate beans:
#Bean
#Primary
public RestTemplate jwtRestTemplate(
RestTemplateBuilder builder,
JWTService jwtService) {
return builder
.additionalInterceptors(
Collections.singletonList(
new JWTHeaderRequestInterceptor(jwtService)
)
)
.build();
}
#Bean
public RestTemplate rawRestTemplate(RestTemplateBuilder builder) {
return builder.build();
}
The first one is primary and the other one is requested by #Qualifier("rawRestTemplate").
However, I'm mocking a ResTemplate into my tests:
#RunWith(SpringRunner.class)
#SpringBootTest()
public class AuditoryTest {
#MockBean()
private RestTemplate frontOfficeRestTemplate;
#Autowired
private DocumentServiceBackOffice documentService;
DocumentServiceBackOffice constructor is:
public DocumentServiceBackOffice(RestTemplate restTemplate);
I'm getting an exception:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in net.gencat.transversal.espaidoc.backoffice.service.DocumentServiceBackOffice required a single bean, but 2 were found:
- rawRestTemplate: defined by method 'rawRestTemplate' in class path resource [net/gencat/transversal/espaidoc/backoffice/config/BackOfficeConfiguration.class]
- jwtRestTemplate: defined by method 'createMock' in null
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
Message is pretty clear, but I don't quite figure out how to solve that.
Any ideas?
I've solved that using that additional Configuration class:
#TestConfiguration
public static class RestTemplateTestConfiguration {
#Bean("jwtRestTemplate")
#Primary
public static RestTemplate someService() {
return Mockito.mock(RestTemplate.class);
}
}

Resources