鸡肉部位英文对照
考虑一个负责进行远程调用和获取详细信息的服务类:
... public class CitiesService { private final WebClient.Builder webClientBuilder; private final String baseUrl; public CitiesService( WebClient.Builder webClientBuilder, @Value ( "${cityservice.url}" ) String baseUrl) { "${cityservice.url}" ) String baseUrl) { this .webClientBuilder = webClientBuilder; this .baseUrl = baseUrl; } public Flux<City> getCities() { return this .webClientBuilder.build() .get() ....
这是一个Spring Bean,它通过称为“ cityservice.url”的属性来解析要调用的URL。
如果要测试此类,则在使用WebClient时一直使用的方法是使用出色的Wiremock启动模拟服务器,并使用它来测试此类。 Wiremock模拟看起来像这样:
private static final WireMockServer WIREMOCK_SERVER = new WireMockServer(wireMockConfig().dynamicPort()); ..... WIREMOCK_SERVER.stubFor(get(urlEqualTo( "/cities" )) .withHeader( "Accept" , equalTo( "application/json" )) .willReturn(aResponse() .withStatus( 200 ) .withHeader( "Content-Type" , "application/json" ) .withBody(resultJson)));
Wiremock服务器正在一个随机端口上启动,并设置为响应名为“ / cities”的终结点。 这是鸡肉和鸡蛋问题的出处:
1. CitiesService类要求在开始测试之前设置名为“ cityservice.url”的属性。
2. Wiremock在一个随机端口上启动,它响应的URL是“ http:// localhost:randomport”,并且仅在测试开始后才可用。
我可以想到三种可能的解决方案来打破这种循环依赖关系:
方法1:使用硬编码端口
这种方法取决于在固定端口而不是动态端口上启动Wiremock,这样可以在启动测试时设置属性,如下所示:
@ExtendWith (SpringExtension. class ) @SpringBootTest (classes = CitiesServiceHardcodedPortTest.SpringConfig. class , properties = "cityservice.url= http://localhost:9876 " ) public class CitiesServiceHardcodedPortTest { private static final WireMockServer WIREMOCK_SERVER = new WireMockServer(wireMockConfig().port( 9876 ));
在此,Wiremock在端口9876上启动,并且启动时的属性设置为“ http:// localhost:9876 /”。
这解决了问题,但是,这不是CI服务器友好的,端口可能在运行时发生冲突,这导致测试不稳定。
方法2:不使用Spring进行测试
更好的方法是按照以下方式不使用该属性:
public class CitiesServiceDirectTest { private static final WireMockServer WIREMOCK_SERVER = new WireMockServer(wireMockConfig().dynamicPort()); private CitiesService citiesService; @BeforeEach public void beforeEachTest() { final WebClient.Builder webClientBuilder = WebClient.builder(); this .citiesService = new CitiesService(webClientBuilder, WIREMOCK_SERVER.baseUrl()); }
这里通过在构造函数中显式设置baseUrl来创建服务,从而避免了在测试之前设置属性的需求。
方法3:应用程序上下文初始化器
ApplicationContextInitializer用于以编程方式初始化Spring Application Context,它可以与测试一起使用,以在执行实际测试之前注入属性。 遵循以下原则:
@ExtendWith (SpringExtension. class ) @SpringBootTest (classes = CitiesServiceSpringTest.SpringConfig. class ) @ContextConfiguration (initializers = {CitiesServiceSpringTest.PropertiesInitializer. class }) public class CitiesServiceSpringTest { private static final WireMockServer WIREMOCK_SERVER = new WireMockServer(wireMockConfig().dynamicPort()); @Autowired private CitiesService citiesService; @Test public void testGetCitiesCleanFlow() throws Exception { ... } static class PropertiesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { TestPropertyValues.of( "cityservice.url=" + " http://localhost: " + WIREMOCK_SERVER.port() ).applyTo(applicationContext.getEnvironment()); } } }
首先启动Wiremock,然后使用初始化程序初始化Spring上下文,该初始化程序使用Wiremocks动态端口注入“ cityservice.url”属性,这种属性可用于连接到CityService。
结论
我个人更喜欢方法2,但是最好在测试之前创建Spring的连线和相关的bean,如果类利用了这些,那么我更喜欢方法3。应用程序上下文初始化器提供了一种解决鸡和鸡蛋问题的好方法。这些属性需要在Spring的上下文参与之前提供。
所有代码示例均在此处提供:
方法1:https://github.com/bijukunjummen/reactive-cities-demo/blob/master/src/test/java/samples/geo/service/CitiesServiceHardcodedPortTest.java
方法2:https://github.com/bijukunjummen/reactive-cities-demo/blob/master/src/test/java/samples/geo/service/CitiesServiceDirectTest.java 方法3:https://github.com/bijukunjummen/reactive-cities-demo/blob/master/src/test/java/samples/geo/service/CitiesServiceSpringTest.java
翻译自: https://www.javacodegeeks.com/2019/08/chicken-egg-resolving-spring-properties-ahead-test.html
鸡肉部位英文对照