Spring boot testing of a rest client using #RestClientTest - java

I am using spring boot 1.5.8 and want to test my client:
#Component
public class RestClientBean implements RestClient {
private Map<String, RestTemplate> restTemplates = new HashMap<>();
#Autowired
public RestClientBean(RestTemplateBuilder builder, SomeConfig conf) {
restTemplates.put("first", builder.rootUri("first").build();
restTemplates.put("second", builder.rootUri("second").build();
}
}
with the following test:
#RunWith(SpringRunner.class)
#RestClientTest(RestClient.class)
public class RestClientTest {
#Autowired
private RestClient client;
#Autowired
private MockRestServiceServer server;
#TestConfiguration
static class SomeConfigFooBarBuzz {
#Bean
public SomeConfig provideConfig() {
return new SomeConfig(); // btw. not sure why this works,
// but this is the only way
// I got rid of the "unable to load
// SomeConfig auto-wire" or something like this :)
// Anyway not my main issue
// EDIT: just realized that the whole
// #TestConfiguration part can be avoided by
// adding SomeConfig.class to the classes in the
// #RestClientTest annotation
}
}
#Before
public void setUp() throws Exception {
server.expect(requestTo("/somePath")) // here an exception is thrown
// (main issue)
.andRespond(withSuccess("<valid json>", MediaType.APPLICATION_JSON));
}
}
The exception is very clear:
java.lang.IllegalStateException: Unable to use auto-configured
MockRestServiceServer since MockServerRestTemplateCustomizer has been bound to
more than one RestTemplate
But is it somehow possible to get this tested or is it not allowed to instantiate two different rest templates in one client class?
I have just the need to use the first rest template in some cases and the second one in some others.

After some days of investigation and communication with spring folks via GitHub I found a solution for me and not receiving an answer here means my solution might be valuable for someone:
#RunWith(SpringRunner.class)
#RestClientTest(RestClient.class)
public class RestClientTest {
#Autowired
private RestClient client;
private MockRestServiceServer firstServer;
private MockRestServiceServer secondServer;
private static MockServerRestTemplateCustomizer customizer;
#TestConfiguration
static class RestTemplateBuilderProvider {
#Bean
public RestTemplateBuilder provideBuilder() {
customizer = new MockServerRestTemplateCustomizer();
return new RestTemplateBuilder(customizer);
}
}
#Before
public void setUp() {
Map<RestTemplate, MockRestServiceServer> servers = customizer.getServers();
// iterate and assign the mock servers according to your own strategy logic
}
#Test
public void someTest() {
firstServer.expect(requestTo("/somePath"))
.andRespond(withSuccess("some json body"),
MediaType.APPLICATION_JSON));
// call client
// verify response
}
Basically specify a number of mock servers same as the number of rest templates you use in your client code, then specify a test configuration providing a rest builder with a customizer, so that your client code's rest templates will be built via this customized builder. Then use the customizer to get the mock servers bounded to the created rest templates and define expectations on them as you want.

Related

Testing Spring Boots caching behavior in a RestClientTest

I have the following scenario:
A Service calls some rest endpoint to query a resource.
The result of this query should be cached for one day and then requeried.
To verify this behavior I wrote a test that calls the service method 2 times and expects that the rest service was actually only queried one time.
Here would be the class that calls the endpoint:
#EnableCaching
#Service
public class CachedResourceClient {
private final RestTemplate restTemplate;
public CachedResourceClient(#Value("${api.host}") String apiHost, RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder
.rootUri(apiHost)
.build();
}
#Cacheable("resource")
public Optional<Resource> fetchResource() {
try {
return Optional.of(restTemplate.getForObject("/resource", Resource.class));
} catch (HttpClientErrorException | HttpServerErrorException | UnknownHttpStatusCodeException e) {
// logging
return Optional.empty();
}
}
}
and then we have this test here:
#RestClientTest(CachedResourceClient.class)
public class CachedResourceClientTests {
#Autowired
private MockRestServiceServer server;
#Autowired
private CachedResourceClient cachedResourceClient;
#Test
public void fetchResource() throws JsonProcessingException {
server.expect(requestTo("/resource"))
.andRespond(withSuccess("{ resource: 'some' }", MediaType.APPLICATION_JSON));
cachedResourceClient.fetchResource();
cachedResourceClient.fetchResource();
server.verify();
}
}
As you can see, we're calling the fetchResource method two times. But the server.verify call fails, because there are also two requests sent to the mock server.
I think the RestClientTest does something behind the scene with my CachedResourceClient that conflicts with the way Caching is setup.
Because if I remove the RestClientTest-Annotation and manually mock the RestTemplateBuilder and the RestTemplate the tests work as expected.
But having a server mock is a bit more comfortable than manually mocking the used classes.
Is there something wrong with the setup?

Mockito - mock ApplicationContext

I have a Springboot application that looks up the bean from the ApplicationContext at runtime based on the input parameter passed by the user. For this method, I am trying to write Mockito test cases but it is not working and throws NullPointerException.
The class which bootstraps the application:
#SpringBootApplication
public class MyApplication {
private static ApplicationContext appContext;
public static void main(String[] args) {
appContext = SpringApplication.run(MyApplication.class, args);
}
public static ApplicationContext getApplicationContext() {
return appContext;
}
}
Class for which I am trying to write the test cases:
#Service
public class Mailbox {
#Autowired
MailProcessor processor;
public void processUserInput(Envelope object) {
processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class));
processor.allocateEnvelopes(object);
}
}
And my test case is as below:
#RunWith(MockitoJUnitRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class MailboxTest {
#Mock
MailProcessor processor;
#InjectMocks
Mailbox mailbox;
#Test
public void testProcessUserInput() {
Envelope message = new Envelope();
message.setAction("userAction");
message.setValue("userInput");
doNothing().when(processor).setCommand(any());
doNothing().when(processor).allocateEnvelopes(any());
mailbox.processUserInput(message);
Mockito.verify(processor).allocateEnvelopes(any());
}
}
Whenever I run the test cases it gives the NullPointerException at processor.setCommand(MyApplication.getApplicationContext().getBean(object.getAction(), Command.class)); in Mailbox class. How can I mock the ApplicationContext lookup? Am I missing any mocking step?
Spring wise your code doesn't look good, and in particular is not unit testable. I'll explain:
Your Mailbox service should not be aware of MyApplication at any level. It is an entry point of spring boot application and your business logic should not depend on that.
Its true that you can inject the application context directly into the class. See an example below. Another (more "old-school") option here is using ApplicationContextAware interface in the Mailbox service (see this example). However, its still a bad code IMO:
#Service
public class Mailbox {
private final ApplicationContext ctx;
...
public Mailbox(ApplicationContext ctx) {
this.ctx = ctx;
}
...
}
Even if you resolve it, in general its not a good idea to depend on the ApplicationContext as well. Because this way you become spring dependent and there is no reason to do that in the Mailbox class. The class will become unit testable though.
In terms of resolution:
In spring you can inject a Map<String, Command> into the mailbox (Its a built-in feature in spring) so that the key of the map will be a bean name, exactly an action of your envelop.
So here is the solution (simplified in places not relevant to injection, just to illustrate the idea):
public interface Command {
void execute();
}
#Component("delete") // note this "delete" word - it will be a key in the map in the Mailbox
public class DeleteMailCommand implements Command {
#Override
public void execute() {
System.out.println("Deleting email");
}
}
#Component("send")
public class SendMailCommand implements Command{
#Override
public void execute() {
System.out.println("Sending Mail");
}
}
Note, that all the commands must be driven by spring (which seems to be your case anyway).
Now, the Mailbox will look like this:
#Service
public class Mailbox {
private final Map<String, Command> allCommands;
private final MailProcessor processor;
// Note this map: it will be ["delete" -> <bean of type DeleteMailCommand>, "send" -> <bean of type SendMailCommand>]
public Mailbox(Map<String, Command> allCommands, MailProcessor mailProcessor) {
this.allCommands = allCommands;
this.processor = mailProcessor;
}
public void processUserInput(Envelope envelope) {
Command cmd = allCommands.get(envelope.getAction());
processor.executeCommand(cmd);
}
}
This solution is easily unit testable, because you can populate the map with mock commands if you wish and there is no need to deal with the application context.
Update
I took a look on your test now, and it's also not really good, sorry :)
#RunWith(MockitoJUnitRunner.class) is used to run unit tests (without spring at all). There is no point in placing this annotation in conjunction with #SpringBootTest which runs a full-fledged system test: starts the whole spring boot application, loads configurations and so forth.
So make sure what kind of tests you want to run and use the appropriate annotations.
Can't say for sure without debugging but it looks like MyApplication.getApplicationContext() is returning null.
Instead of storing it in a static variable you should try injecting the ApplicationContext in your #Service class where you need it:
#Autowired
private ApplicationContext appContext;
Try initializing mailbox object by injecting processor before first test.
mailbox = new Mailbox(processor);

Can't mock repository when testing with mockmvc

I have this quite simple controller class and a simple (jpa) repository.
What I want to do is to test it's api but mock it's repository and let it return an object or not depending on the test case.
My problem now is that I don't know how to do that.
I know how to mock a repository and inject it to a controller/service class with #Mock / #InjectMocks / when().return()
But I fail when I want to do the same after doing a request with MockMvc.
Any help is highly appreciated
The controller
import java.util.Optional;
#RestController
#Slf4j
public class ReceiptController implements ReceiptsApi {
#Autowired
private ReceiptRepository receiptRepository;
#Autowired
private ErrorResponseExceptionFactory exceptionFactory;
#Autowired
private ApiErrorResponseFactory errorResponseFactory;
#Override
public Receipt getReceipt(Long id) {
Optional<ReceiptEntity> result = receiptRepository.findById(id);
if (result.isEmpty()) {
throw invalid("id");
}
ReceiptEntity receipt = result.get();
return Receipt.builder().id(receipt.getId()).purchaseId(receipt.getPurchaseId()).payload(receipt.getHtml()).build();
}
private ErrorResponseException invalid(String paramName) {
return exceptionFactory.errorResponse(
errorResponseFactory.create(HttpStatus.NOT_FOUND.value(), "NOT_VALID", String.format("receipt with id %s not found.", paramName))
);
}
}
And it's test class
#WebMvcTest(ReceiptController.class)
#RestClientTest
public class ReceiptControllerTest {
#InjectMocks
private ReceiptController receiptController;
#Mock
private ReceiptRepository receiptRepository;
#Mock
private ErrorResponseExceptionFactory exceptionFactory;
#Mock
private ApiErrorResponseFactory errorResponseFactory;
private MockMvc mvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(
new ReceiptController())
.build();
}
#Test
public void getReceiptNotFoundByRequest() throws Exception {
mvc.perform(MockMvcRequestBuilders
.get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
//TODO: Finish this test
#Test
public void getReceiptFoundByRequest() throws Exception {
ReceiptEntity receipt1 = ReceiptEntity.builder().id(99999L).purchaseId(432L).html("<html>").build();
when(receiptRepository.findById(1L)).thenReturn(Optional.of(ReceiptEntity.builder().id(1L).purchaseId(42L).html("<html></html>").build()));
ResultActions result = mvc.perform(get("/receipt/1")
.header("host", "localhost:1348")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
Within your setUp() method, you're using Mockito to mock the beans annotated with #Mock and inject them in a new instance of ReceiptController, which is then put into the field annotated with #InjectMocks.
On the next line, you're setting up MockMvc with a new instance of ReceiptController (you use new ReceiptController()), rather than using the instance that Mockito created. That means that all fields within that instance will be null.
This pretty much boils down exactly to Why is my Spring #Autowired field null.
To solve this, you could pass receiptController to MockMvc. In that case, you could also remove the #WebMvcTest and #RestClientTest as you're not using them.
Alternatively, you could setup your test with #RunWith(SpringRunner.class), and properly set up a Spring test by using #Autowired in stead of #InjectMocks and #MockBean in stead of #Mock. In that case, you don't need a setUp() method at all, as MockMvc could be autowired by Spring.
#WebMvcTest and MockMvc allows you to do integration testing, not unit testing.
They allow you to boot an actual Spring application (using a random port) and test the actual controller class with its dependencies. This means that the variables you declared at the top of your test are not actually used in your current setup.
If you wish to unit-test your controller, you can remove #WebMvcTest, create mock dependencies (like you did) and call your methods directly instead of using MockMvc.
If you really wish to write an integration test, you need to mock your external dependencies (the database for example). You can configure spring to use an embedded database (H2 for example) in the test environment, so that you do not affect your real database.
See an example here : https://www.baeldung.com/spring-testing-separate-data-source

Test method with okhttp3 call

I have a method in the service class that makes a call to an external api. How would I mock this okHttpClient call? I have tried to do so with mockito but no luck with that.
//this is the format of the method that i want to test
public string sendMess(EventObj event) {
OkHttpClient client = new OkHttpClient();
//build payload using the information stored in the payload object
ResponseBody body =
RequestBody.create(MediaType.parse("application/json"), payload);
Request request = //built using the Requestbody
//trying to mock a response from execute
Response response = client.newCall(request).execute();
//other logic
}
I am open to refactoring the service class if it helps with the testing. Any suggestions and recommendation is appreciated. Thanks.
Since you are using spring-boot leave managing beans to spring.
1) First create OkHttpClient as spring bean so that you can use it all over application
#Configuration
public class Config {
#Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient();
}
}
2) And then in the service class #Autowire OkHttpClient and use it
#Service
public class SendMsgService {
#Autowired
private OkHttpClient okHttpClient;
public string sendMess(EventObj event) {
ResponseBody body = RequestBody.create(MediaType.parse("application/json"), payload);
Request request = //built using the Requestbody
//trying to mock a response from execute
Response response = okHttpClient.newCall(request).execute();
//other logic
}
}
Tests
3) Now in the Test classes use #SpringBootTest,#RunWith(SpringRunner.class) and #MockBean
The #SpringBootTest annotation can be used when we need to bootstrap the entire container. The annotation works by creating the ApplicationContext that will be utilized in our tests.
#RunWith(SpringRunner.class) is used to provide a bridge between Spring Boot test features and JUnit. Whenever we are using any Spring Boot testing features in out JUnit tests, this annotation will be required.
#MockBean Annotation that can be used to add mocks to a Spring ApplicationContext.
#SpringBootTest
#RunWith(SpringRunner.class)
public class ServiceTest {
#Autowire
private SendMsgService sendMsgService;
#MockBean
private OkHttpClient okHttpClient;
#Test
public void testSendMsg(){
given(this.okHttpClient.newCall(ArgumentMatchers.any())
.execute()).willReturn(String);
EventObj event = //event object
String result = sendMsgService.sendMess(event);
}
}
I would suggest, that you pull out the instantiation of your OkHttpClient to an own method in a Configuration class. Afterwards you could #Inject the client anywhere it is needed and testing becomes much easier because you can #Mock it away.
Such to say the Spring-managed bean:
#Configuration
public class OkHttpClientConfiguration {
#Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient();
}
}
…your production class:
#Component
public class ProductionClass {
#Inject
private OkHttpClient okHttpClient;
public string sendMess(EventObj event) {
okHttpClient // whatever you want
[…]
}
}
…and your test:
public class SpyTest {
#InjectMocks
private ProductionClass productionClass;
#Mock
private OkHttpClient okHttpClient;
#Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
#Test
public void spyInsteadOfPowermock() {
Request request = // mock the request
when(okHttpClient.newCall(request)).thenReturn(mock(Call.class));
}
}

Spring-Boot RestClientTest not correctly auto-configuring MockRestServiceServer due to unbound RestTemplate

EDIT: This question is specifically pertaining to the #RestClientTest annotation introduced in spring-boot 1.4.0 which is intended to replace the factory method.
Problem:
According to the documentation the #RestClientTest should correctly configure a MockRestServiceServer to use when testing a REST client. However when running a test I am getting an IllegalStateException saying the MockServerRestTemplateCustomizer has not been bound to a RestTemplate.
Its worth noting that I'm using Gson for deserialization and not Jackson, hence the exclude.
Does anyone know how to correctly use this new annotation? I haven't found any examples that require more configuration then when I have already.
Configuration:
#SpringBootConfiguration
#ComponentScan
#EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class})
public class ClientConfiguration {
...
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
}
Client:
#Service
public class ComponentsClientImpl implements ComponentsClient {
private RestTemplate restTemplate;
#Autowired
public ComponentsClientImpl(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
public ResponseDTO getComponentDetails(RequestDTO requestDTO) {
HttpEntity<RequestDTO> entity = new HttpEntity<>(requestDTO);
ResponseEntity<ResponseDTO> response =
restTemplate.postForEntity("/api", entity, ResponseDTO.class);
return response.getBody();
}
}
Test
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private MockRestServiceServer server;
#Test
public void getComponentDetailsWhenResultIsSuccessShouldReturnComponentDetails() throws Exception {
server.expect(requestTo("/api"))
.andRespond(withSuccess(getResponseJson(), APPLICATION_JSON));
ResponseDTO response = client.getComponentDetails(requestDto);
ResponseDTO expected = responseFromJson(getResponseJson());
assertThat(response, is(expectedResponse));
}
}
And the Exception:
java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate
Answer:
As per the answer below there is no need to declare a RestTemplateBuilder bean into the context as it is already provided by the spring-boot auto-configuration.
If the project is a spring-boot application (it has #SpringBootApplication annotation) this will work as intended. In the above case however the project was a client-library and thus had no main application.
In order to ensure the RestTemplateBuilder was injected correctly in the main application context (the bean having been removed) the component scan needs a CUSTOM filter (the one used by #SpringBootApplication)
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})
The MockRestServiceServer instance should be constructed from the static factory, using a RestTemplate. See this article for a detailed description of the testing process.
In your example, you can do:
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private RestTemplate template;
private MockRestServiceServer server;
#Before
public void setUp() {
server= MockRestServiceServer.createServer(restTemplate);
}
/*Do your test*/
}
You have RestTemplateBuilder at two places. At ClientConfiguration class and at ComponentsClientImpl class. Spring boot 1.4.0 auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. Remove below code from ClientConfiguration class and run your test.
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}

Categories

Resources