Spring Boot mocking other rest clients when testing - java

I'm consuming a remote api in my Spring MVC project using RestTemplate. One client is for authentication named LoginApiClient and I have to use this in all other rest clients.
This one my rest client that uses LoginApiClient and consumes QrCode Api.
#Service
public class QrKeyApiClient implements QrKeyApiClientBase {
private RestTemplate restTemplate;
#Autowired
private LoginApiClientBase loginApiClient;
public QrKeyApiClient(RestTemplateBuilder builder) {
restTemplate = builder.build();
restTemplate.setErrorHandler(new ErrorHandler());
}
//Other implementation details
}
And this is LoginApiClient
#Service
public class LoginApiClient implements LoginApiClientBase {
private RestTemplate restTemplate;
public LoginApiClient(RestTemplateBuilder builder) {
restTemplate = builder.build();
restTemplate.setErrorHandler(new ErrorHandler());
}
//Other implementation details
}
My unit test class for QrKeyApiClient is like below.
#RunWith(SpringRunner.class)
#RestClientTest({QrKeyApiClient.class})
#Category(IUnitTest.class)
public class QrKeyApiClientTest {
#Value("${getQrCodeUrl}")
private String getQrCodeUrl;
#Mock
LoginApiClient loginApiClient;
#Autowired
private MockRestServiceServer server;
#InjectMocks
private QrKeyApiClient client;
#Test
public void getQrCodeAsImage_makesTrueCallToApi() throws Exception {
ResponseEntity<String> responseEntity = mock(ResponseEntity.class);
HttpEntity requestEntity = new HttpEntity<>(qrCodeGenerateRequest,new HttpHeaders());
this.server
.expect(requestTo(this.getQrCodeUrl))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("successResult", MediaType.TEXT_PLAIN));
String qrImage = this.client.getQrCodeAsImage(qrCodeGenerateRequest);
server.verify();
assertThat(qrImage, is(notNullValue(String.class)));
}
}
I'm not sure this is the right way to do this but I want to mock my LoginApiClient in this test and inject it to QrKeyApiClient. But my test not passes with an error that says "Error creating bean with name 'qrKeyApiClient': Unsatisfied dependency expressed through field 'loginApiClient';"
So how I can test this client with mock of other clients that member of it.

It seems the problem is a mixed usage of Spring and Mockito annotations. Spring provides the #MockBean annotation to mock services in the application context.
Try replacing #Mock with #MockBean and #InjectMocks with #Autowired. The requestEntity and responseEntity are not needed then probably.
#RunWith(SpringRunner.class)
#RestClientTest({QrKeyApiClient.class})
#Category(IUnitTest.class)
public class QrKeyApiClientTest {
#Value("${getQrCodeUrl}")
private String getQrCodeUrl;
#MockBean
LoginApiClient loginApiClient;
#Autowired
private MockRestServiceServer server;
#Autowired
private QrKeyApiClient client;
#Test
public void getQrCodeAsImage_makesTrueCallToApi() throws Exception {
this.server
.expect(requestTo(this.getQrCodeUrl))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("successResult", MediaType.TEXT_PLAIN));
String qrImage = this.client.getQrCodeAsImage(qrCodeGenerateRequest);
server.verify();
assertThat(qrImage, is(notNullValue(String.class)));
}
}

Related

Understanding #MockBean usage With 'RestTemplate restTemplate'

The behavior that I am seeing is a NullPointerException in the second test when the mocked restTemplate called. That pointed to a problem in the resetting of the mock. What surprised me is the fix ( that made both tests pass).
Modifying the code from
#MockBean private RestTemplate restTemplate;
to
#MockBean(reset = MockReset.NONE) private RestTemplate restTemplate;
fixed the issue. A few questions here:
Why didn't the default #MockBean behavior of MockReset.RESET work?
Is there something wrong with how I have set up my test such that default MockReset.RESET was failing?
Is there something wrong with the test config class?
Hopefully, I've provided enough context to answer the question.
I've created a simplified example of what I'm seeing:
Test configuration:
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
#Primary
public ObjectNode getWeatherService(RestTemplate restTemplate) {
return new WeatherServiceImpl(restTemplate);
}
}
The test:
#SpringBootTest
#ActiveProfiles("test")
#AutoConfigureMockMvc
class SamTest {
#Autowired private MockMvc mockMvc;
#MockBean private RestTemplate restTemplate;
/*
Works:
#MockBean(reset = MockReset.NONE) private RestTemplate restTemplate;
Fails:
#MockBean(reset = MockReset.BEFORE) private RestTemplate restTemplate;
#MockBean(reset = MockReset.AFTER) private RestTemplate restTemplate;
*/
#Test
public void testOne() throws Exception {
Mockito.when(restTemplate.getForEntity("http://some.weather.api", ObjectNode.class))
.thenReturn(new ResponseEntity("{\"weather\" : \"rainy\"}", HttpStatus.OK));
// Makes call to standard #RestController with a #GetMapping
// Call to external API is contained in #Service class.
// Currently controller just passes through the json from the underlying service call.
this.mockMvc.perform(
get("/weather/check").
contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(status().isOk());
}
#Test
public void testTwo() throws Exception {
Mockito.when(restTemplate.getForEntity("http://some.weather.api", ObjectNode.class))
.thenReturn(new ResponseEntity("{\"error\" : \"bandwidth\"}", HttpStatus.BANDWIDTH_LIMIT_EXCEEDED));
this.mockMvc.perform(
get("/weather/check").
contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(status().is5xxServerError());
}
}
The service:
#Service
public class WeatherServiceImpl implements WeatherService {
private final RestTemplate restTemplate;
#Autowired
public WeatherServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public ObjectNode retrieve(URI uri) {
ResponseEntity<ObjectNode> response = restTemplate.getForEntity(uri, ObjectNode.class);
return response.getBody();
}
}
There is a misunderstanding about the #MockBean default behaviour:
Why didn't the default #MockBean behavior of MockReset.RESET work? Is
there something wrong with how I have set up my test such that default
MockReset.RESET was failing?
From the MockBean.reset method documentation:
The reset mode to apply to the mock bean. The default is
MockReset.AFTER meaning that mocks are automatically reset after each
test method is invoked.
So your MockBean will be reset and unregistered from the application context after your first testcase execution and then your second testcase will find it null, while it will not happen in case of #MockBean(reset = MockReset.NONE) as you have done.

JUnit | MockServer | RestTemplate

I have one service called PostService, which has instance variable called connectionManager Autowired.
Inside this connectionManager there is a one instance variable called restTemplate Autowired.
And in the configuration java file, object of RestTemplate is created with some logic.
When I write a test case around it, and create a MockRestServiceServer with an expected URL and post method, and in return expect a response with some body. and when I execute a test case I don't get mocked response from this mockedRestServiceServer.
As much, I can sense, this is because during test execution, a real object of rest template is created and mock server is not used to send mocked response.
can someone help me, how to overcome from this ?
class PostServie {
#Autowired
private ConnectionManager connectionManager;
public void postMessage(String msg) {
// some logic
}
}
#Component
class ConnectionManager {
#Autowired
private RestTemplate restTemplate;
public String getToken(){
ResponseEntity<String> response = this.restTemplate.postForEntity(url, request, String.class);
//returns response.body() in string format
}
}
#Configuration
class Configuration {
#Bean
public RestTemplate getRestTemplate(){
// some logic and returns object of RestTemplate
}
}
#RunWith(SpringRunner.class)
class PostServiceTest {
#Autowired
private PostMessageService postMessageService;
#Resource(name="authServerRestTemplet")
private RestTemplate authServerRestTemplet;
private MockRestServiceServer mockedAuthServerRestTemplet;
private final String requestToAuthServer ="grant_type=client_credentials&client_id=ceapiClientId";
#Before
public void setUp() {
mockedAuthServerRestTemplet =
MockRestServiceServer.createServer(authServerRestTemplet);
}
#Test
public void postServeiceSuccess () {
mockedAuthServerRestTemplet.expect(requestTo(ACCESS_TOKEN_URI)).andExpect(content().string(requestToAuthServer)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess("{abc : 'abc'}", MediaType.APPLICATION_JSON));
postMessageService.postMessage(jsonMessage);
}
}
}
You're creating an instance of RestTemplate but not using it. What is MockRestServiceServer doing?
I can also see a wrong utilisation of #Resource and #Autowired, which you shouldn't specify in your test. Try use #MockBean.
Autowiring works so that the resource you create in your test is injected in the real class, not the other way round.
I'll just add some sample code here, which might help you achieve your goal.
#RunWith(SpringRunner.class)
#WebMvcTest
public abstract class AbstractControllerTest {
#Autowired
protected MockMvc mockMvc;
#MockBean
protected Service service;
#Before
public void setUp() {
Mockito.reset(service);
}
}
and then your test
public class YourTest extends AbstractControllerTest {
#Test
public void shouldDoSomething() throws Exception {
// given
List<someStuff> stuff = new ArrayList<>();
stuff.add(new Whatever("content", "John Smith");
// when - service is in the abstract controller!
when(service.getSomething()).thenReturn(stuff);
// then
mockMvc.perform(get("/posts/1/whatever").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content", is("content")))
.andExpect(jsonPath("$[0].author", is("John Smith"))));
}

Spring Feign configuration: how to test all #Bean methods are called

I want to configure Spring feign with a configuration class, and I want to make sure that all the #Bean methods are called when Spring configures the feign client for me.
How to test it?
For example, I have:
#FeignClient(
name = "PreAuthSendRequest",
url = "${xxx.services.preauth.send.url}",
configuration = AppFeignConfig.class)
public interface RequestService {
#PostMapping("")
#Headers("Content-Type: application/json")
PreAuthResponse execute(#RequestBody PreAuthRequest preAuthRequest);
}
And AppFeignConfig.java:
#Configuration
#RequiredArgsConstructor
public class AppFeignConfig{
private final HttpClient httpClient;
private final Jackson2ObjectMapperBuilder contextObjectMapperBuilder;
#Bean
public ApacheHttpClient client() {
return new ApacheHttpClient(httpClient);
}
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder((ObjectMapper)contextObjectMapperBuilder.build());
}
#Bean
public Encoder feignEncoder() {
return new JacksonEncoder((ObjectMapper)contextObjectMapperBuilder.build());
}
#Bean
public Retryer retryer() {
return Retryer.NEVER_RETRY;
}
#Bean
public ErrorDecoder errorDecoder() {
return new ServiceResponseErrorDecoder();
}
}
So, how to verify that all #Bean methods are called? I know #MockBean, but what I want to check is config.feignDecoder(), etc., are indeed called.
When I am trying to context.getBean(RequestService.class); and call execute() method, it seems to send a real request and without wiremock, it fails, obviously.
For now I have this:
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
class RequestServiceTest {
#Autowired
private ApplicationContext applicationContext;
#MockBean
private ApacheHttpClient client;
#MockBean
private Decoder feignDecoder;
#MockBean
private Encoder feignEncoder;
#MockBean
private Retryer retryer;
#MockBean
private ErrorDecoder errorDecoder;
#Test
void shouldRetrieveBeansFromApplicationContextToConstructConfigurationInstance() {
AppFeignConfig config = applicationContext.getBean(AppFeignConfig.class);
Assertions.assertEquals(config.feignEncoder(), feignEncoder);
Assertions.assertEquals(config.feignDecoder(), feignDecoder);
Assertions.assertEquals(config.errorDecoder(), errorDecoder);
Assertions.assertEquals(config.client(), client);
Assertions.assertEquals(config.retryer(), retryer);
}
}
I don't know if it is how it should be. If any idea, please comment.

Spring boot RestEasy Post method Expected :201 Actual :404

I have a spring boot application which contains a RestEasy webservice created using #Service like:
#Path("/developers")
#Service
public interface DeveloperResource {
#POST
#Produces("application/json")
#Consumes("application/json")
Response create(#RequestBody List<DeveloperDto> developers);
}
and I have the the according integration test class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
public class DeveloperResourceTest {
public static final String URI = "http://localhost:8080/developers";
public static final DeveloperDto DEVELOPER = new DeveloperDto(null, "toto");
public static final List<DeveloperDto> DEVELOPERS_COLLECTION = Collections.singletonList(DEVELOPER);
public static final DeveloperEntity DEVELOPER_MAPPED_TO_ENTITY = DeveloperMapper.toEntity(DEVELOPER);
public static final String DEVELOPER_COLLECTION_IN_JSON = "[{\"developerId\":null,\"developerName\":\"toto\",\"programmingLanguages\":null}]";
private MockMvc mockMvc;
#MockBean
DeveloperService service;
#Mock
private DeveloperResource tested;
#Autowired
WebApplicationContext webApplicationContext;
private ObjectMapper mapperJson;
#Before
public void setUp() throws Exception {
tested=new DeveloperResourceImpl(service);
mockMvc=MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
mapperJson = new ObjectMapper();
}
#Test
public void should_create_a_developer_and_return_OK() throws Exception {
Mockito.when(service.save(DEVELOPER_MAPPED_TO_ENTITY)).thenReturn(Optional.of(DEVELOPER_MAPPED_TO_ENTITY));
tested.create(DEVELOPERS_COLLECTION);
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post(URI)
.content(DEVELOPER_COLLECTION_IN_JSON)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
assertEquals(HttpStatus.CREATED.value(), response.getStatus());
assertEquals(URI, response.getHeader(HttpHeaders.LOCATION));
}
}
After the execution of the test I got:
java.lang.AssertionError:
Expected :201
Actual :404
My questions are:
Is my integration test configuration apropriate?
Can we use MockMvc even if the created REST web service isn't created using #RestController or #Controller?.
Thank you in advance
#Service will not be recognized as a rest service. You should annotate it as #RestController on a class, actually implementing the create method.
see: #Service
You can’t use MockMvc to test an app that doesn’t use Spring MVC. RESTdocs has support for Rest Assured which works over HTTP so it doesn’t depend on the web stack.

How to mock RestTemplate with MockRestServiceServer?

#RunWith(MockitoJUnitRunner.class)
public class FeatureFlipperManagerTest {
#Autowired
RestTemplate restTemplate = new RestTemplate();
#Autowired
Service service = new Service();
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
#Test
public void test() throws Exception {
mockServer.expect(requestTo(Mockito.anyString()))
.andRespond(withSuccess("{\"enabled\":true}", MediaType.APPLICATION_JSON));
boolean res = service.isEnabled("xxx");
mockServer.verify();
Assert.assertEquals(true, res);
}
}
I have MockRestServiceServer to mock restTemplete in a service. But it always fail. it shows the error as java.lang.AssertionError: Further request(s) expected
0 out of 1 were executed. Any one could let me know where I did not do it right.
The service itself will looks as this:
public class Service{
public boolean isEnabled(String xxx) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity("someurl",String.class);
if(...)return true;
return false;
}
}
First of all, your Service class creates a new instance of RestTemplate on every request. I cannot stress enough how bad practice it is. Create a bean of type RestTemplate and inject it into your Service bean (it is most likely already created - depending on the Spring MVC version you are using).
Once you have it, then both RestTemplates: one in your Service bean and one injected into FeatureFlipperManagerTest will be the same and testing with MockRestServiceServer will be possible.
EDIT - to be more explicit:
Modify your Service class to:
#Component
public class Service {
private RestTemplate restTemplate;
#Autowired
public Service(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public boolean isEnabled(String xxx) {
ResponseEntity<String> response = restTemplate.getForEntity("someurl",String.class);
if(...)return true;
return false;
}
}
and your test class to:
#RunWith(MockitoJUnitRunner.class)
public class FeatureFlipperManagerTest {
#Autowired
RestTemplate restTemplate;
#Autowired
Service service;
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
#Test
public void test() throws Exception {
mockServer.expect(requestTo(Mockito.anyString()))
.andRespond(withSuccess("{\"enabled\":true}", MediaType.APPLICATION_JSON));
boolean res = service.isEnabled("xxx");
mockServer.verify();
Assert.assertEquals(true, res);
}
}
If this fails with exception saying that there is no RestTemplate bean present then please paste info about version of Spring (Spring Boot?) you are using.
I think you mean you want to use RestTemplate which is provided by spring, so you should createServer after the spring autowired the RestTemplate. I think you can do it like this:
#RunWith(MockitoJUnitRunner.class)
public class FeatureFlipperManagerTest {
#Autowired
RestTemplate restTemplate;
Service service;
MockRestServiceServer mockServer;
#Before
public void init() {
service = new Service();
service.setRestTemplate(restTemplate);
// If you have autowired restTemplate in Service, you can just autowired the service
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void test() throws Exception {
mockServer.expect(requestTo(Mockito.anyString()))
.andRespond(withSuccess("{\"enabled\":true}", MediaType.APPLICATION_JSON));
boolean res = service.isEnabled("xxx");
mockServer.verify();
Assert.assertEquals(true, res);
}
}
This is not an answer to your question, but just in case anyone comes across this question in 2021… With Spring Boot Testing, you may want to take advantage of testing the REST slice only with #RestClientTest. This creates a RestTemplateBuilder bean only by default, if you want an auto-wired RestTemplate just add one bit of configuration as below. (The example is in Kotlin, using Java instead remains as an excercise to the reader.)
#AutoConfigureWebClient(registerRestTemplate = true)
#RestClientTest(Service::class)
class AdkClientTest #Autowired constructor(
private val mockRestServiceServer: MockRestServiceServer,
private val service: Service
) {
// …
}

Categories