如何在Spring上下文初始化之前模拟REST服务器?

时间:2018-04-05 11:10:21

标签: java spring unit-testing junit

我有一个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 {

但它没有改变任何东西。

提前致谢。

3 个答案:

答案 0 :(得分:2)

此设置适用于我:

@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);
    }

}

当且仅当弹簧上下文完全初始化时才会调用

并在测试类中:

@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() {

    }

}

这非常繁琐,如果您可以重构代码并避免在初始化设置期间调用休息服务,而是在某些业务事件中,它肯定会更好

答案 1 :(得分:0)

我找到了一种方法(如果有人有更好的解决方案,请留下另一个答案)。 @RestClientTest执行此操作非常接近本案所需但尚不够。我们需要RestTemplateBuilder始终生成相同的RestTemplate,我们还需要绑定到MockRestServiceServer的{​​{1}}。我通过用模拟替换RestTemplate Bean定义并在bean定义中预先配置了RestTemplateBuilder权限来确保在发送任何请求之前执行此代码。

MockRestServiceServer

应用程序启动成功。

答案 2 :(得分:0)

看看我在 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);
  }
}

然后就很容易测试了。