У меня есть Spring Bean, который отправляет запрос внутри своего конструктора с помощью RestTemplateBuilder. Во время тестирования я хотел бы смоделировать ответ с помощью MockRestServiceServer (или, может быть, смоделировать RestTemplateBuilder, который используется для отправки запроса) с некоторыми предопределенными данными только для загрузки контекста приложения. Адрес записан в файл application.properties.
Я пробовал это сделать:
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() {
}
}
Но контекст загружается до выполнения метода @Before и завершается ошибкой с сообщением о том, что MockRestServiceServer не ожидал запроса.
Затем я попробовал использовать 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));
}
}
Но затем он жалуется, что MockServerRestTemplateCustomizer has not been bound to a RestTemplate. Я подумал, что эту проблему можно решить, используя аннотацию @RestClientTest в тестовом классе, поскольку она отключит автоматическую настройку RestTemplateBuilder и позволит настроить MockRestServiceServer:
@RunWith(SpringRunner.class)
@SpringBootTest
@RestClientTest
@ContextConfiguration(initializers = AppInit.class)
public class CollectorApplicationTest {
Но это ничего не изменило.
Заранее спасибо.




эта настройка работает для меня:
@Service
public class MyService implements ApplicationListener<ContextRefreshedEvent> {
private final RestTemplate restTemplate;
public MyService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
//make your call here
restTemplate.getForEntity("http://www.localhost:9090", String.class);
}
}
Это вызывает вызов тогда и только тогда, когда контекст Spring полностью инициализирован.
и в тестовых классах:
@TestConfiguration
public class MockServiceCallConfiguration {
@Autowired
private RestTemplate restTemplate;
@Bean
public MockRestServiceServer mockRestServiceServer() {
MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
server.expect(MockRestRequestMatchers.requestTo("http://www.localhost:9090"))
.andRespond(withSuccess("{}", MediaType.APPLICATION_JSON));
return server;
}
}
@SpringBootTest(classes = {MockServiceCallConfiguration.class})
@RunWith(SpringRunner.class)
public class MyServiceTest {
@Test
public void test() {
}
}
Это довольно утомительно, если вы можете реорганизовать свой код и избежать вызова службы отдыха во время настройки инициализации, а скорее на каком-то бизнес-мероприятии, это наверняка будет лучше
@Sam без проблем дайте мне знать
Не уверен, что это меняет. @PostConstruct запускается сразу после конструктора, поэтому абсолютно ничего не изменилось.
@Sam извините, похоже, я неправильно понял ваш вариант использования, я публикую новое решение. Я видел, что ты нашел свой, выбери для себя лучшее
Спасибо за обновление ответа! Я не могу выполнить рефакторинг кода, потому что приложение должно получить некоторые настройки от другого компонента через HTTP, чтобы иметь возможность функционировать, поэтому необходимо разместить его при запуске. Кроме того, вы используете RestTemplate, а я - RestTemplateBuilder, так что это немного другое. Я подумаю о переносе запроса в метод слушателя, спасибо
Я нашел способ (если у кого-то есть решение получше, оставьте, пожалуйста, другой ответ). @RestClientTest делает это, что довольно близко к тому, что необходимо в данном случае, но недостаточно. Нам нужен RestTemplateBuilder, чтобы всегда генерировать один и тот же RestTemplate, и нам также нужен MockRestServiceServer, связанный с этим RestTemplate. Я сделал это, заменив определение компонента RestTemplateBuilder на макет, а также предварительно сконфигурировав MockRestServiceServer прямо в определении компонента, чтобы убедиться, что этот код выполняется до отправки любых запросов.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
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 org.springframework.web.client.RestTemplate;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
@RunWith(SpringRunner.class)
@SpringBootTest
public class CollectorApplicationTest {
@Test
public void contextLoads() {
}
@TestConfiguration
static class A {
@Primary
@Bean
public RestTemplateBuilder restTemplateBuilder(@Value("${components.web-admin-portal.rest.schemas}")
String webAdminPortal) {
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
server.expect(MockRestRequestMatchers.requestTo(webAdminPortal))
.andRespond(withSuccess("{}", MediaType.APPLICATION_JSON));
RestTemplateBuilder mockBuilder = mock(RestTemplateBuilder.class);
when(mockBuilder.build()).thenReturn(restTemplate);
return mockBuilder;
}
}
}
Таким образом приложение запустится успешно.
Глядя на свой старый вопрос в 2021 году, я бы сделал это по-другому.
Кажется, мне нужно было получить некоторые настройки с удаленного сервера вроде этого (псевдокод ниже):
@Service
class MyService {
MySettingsFromRemote settings;
MyService(RestTemplateBuilder builder, @Value("${my-url}") String url){
var rt = builder.build();
setting = rt.getForEntity(url, MySettingsFromRemote.class);
}
...
}
Поэтому вместо этого я бы реорганизовал код следующим образом:
@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);
}
}
Тогда это очень легко проверить.
Я попробую это завтра и предоставлю обновление, спасибо за идею!