I have a Spring Bean that sends a request inside its constructor using RestTemplateBuilder
in order to send a request:
@Service
class MyService {
MySettingsFromRemote settings;
MyService(RestTemplateBuilder builder, @Value("${my-url}") String url){
var rt = builder.build();
setting = rt.getForEntity(url, MySettingsFromRemote.class);
}
...
}
During testing, I would like to mock the response using MockRestServiceServer
(or maybe mock the RestTemplateBuilder
that is used to send the request) with some predefined data just so the application context loads. The address is written in the application.properties
file.
I tried doing this:
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.client.AutoConfigureMockRestServiceServer;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@RunWith(SpringRunner.class)
@AutoConfigureMockRestServiceServer
@SpringBootTest
public class CollectorApplicationTest {
@Autowired
MockRestServiceServer server;
@Value("${components.web-admin-portal.rest.schemas}")
String webAdminPortal;
@Before
public void init() {
server.expect(MockRestRequestMatchers.requestTo(webAdminPortal))
.andRespond(withSuccess("{}", MediaType.APPLICATION_JSON));
}
@Test
public void contextLoads() {
}
}
But the context loads before the @Before
method is executed and fails with a message saying the MockRestServiceServer
didn't expect the request.
Then I tried using ApplicationContextInitializer
:
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.match.MockRestRequestMatchers;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
public class AppInit implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(final ConfigurableApplicationContext context) {
context.refresh();
MockRestServiceServer server = context.getBean(MockRestServiceServer.class);
String webAdminPortal = context.getEnvironment()
.getProperty("components.web-admin-portal.rest.schemas");
server.expect(MockRestRequestMatchers.requestTo(webAdminPortal))
.andRespond(withSuccess("{}", MediaType.APPLICATION_JSON));
}
}
But then it complains that the MockServerRestTemplateCustomizer has not been bound to a RestTemplate
. I figured that this issue may be resoled by using @RestClientTest
annotation on the test class since it would disable auto configuration of RestTemplateBuilder
and enable confiration of MockRestServiceServer
:
@RunWith(SpringRunner.class)
@SpringBootTest
@RestClientTest
@ContextConfiguration(initializers = AppInit.class)
public class CollectorApplicationTest {
But it did not change anything.
Thanks in advance.
Looking at my old question in 2021, I'd do it differently now.
It seems I needed to get some settings from a remote server like this (pseudo code below):
@Service
class MyService {
MySettingsFromRemote settings;
MyService(RestTemplateBuilder builder, @Value("${my-url}") String url){
var rt = builder.build();
setting = rt.getForEntity(url, MySettingsFromRemote.class);
}
...
}
So, instead of doing that, I'd reorganize the code like this:
@Service
class MyService {
MySettingsFromRemote settings;
MyService(MySettingsFromRemote settings){
this.settings = settings;
}
...
}
@Configuration
class MyConfig {
// maybe Spring provides this bean now?
// rest template is kinda outdated already, webclient is preferred.
@Bean RestTemplate myRestTemplate(RestTemplateBuilder builder) {
return builder.build();
}
@Bean MySettingsFromRemote mySettingsFromRemote(RestTemplate restTemplate, @Value("${my-url}") String url){
return restTemplate.getForEntity(url, MySettingsFromRemote.class);
}
}
Then it's very easy to test. Just need to provide a @MockBean
of MySettingsFromRemote
.