单元测试Spring 云网关RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
Unit test Spring Cloud Gateway RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
我想使用 JUnit5 对 Spring Cloud Gateway RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { 方法进行正确的单元测试。
但是,我很难弄清楚要测试什么、断言什么、模拟什么、如何提高覆盖率等等...
如果可能的话,我只想对此进行单元测试,无需开始整个 SpringTest 等
@Bean
@Override
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route("forward_to_service_one", r -> r.path("/serviceone/**").and().uri("http://the-first-service:8080"))
.route("forward_to_service_two", r -> r.path("/servicetwo/**").and().uri("http://the-second-service:8080"))
.route("forward_to_service_three", r -> r.alwaysTrue().and().order(Ordered.LOWEST_PRECEDENCE).uri("http://the-default-third-service:8080"))
.build();
}
在进行集成测试时,点击端点上启动的网关服务,看到转发到各个服务的请求,我想知道是否有测试此 Spring 云网关功能的良好做法.
请提供完全覆盖的测试用例示例?
谢谢
我无法理解你的测试场景(你想测试什么,是否为路径正确配置了服务?)但我想向你展示两种方法,第一种是基本的,第二种如果你需要更多的控制,那就更复杂了。
简单
这很简单,我正在向我的 SpringBootTest 属性添加一些路由,我使用 Spring 提供的 WebTestClient 实用程序来对 Netty 进行反应性测试。然后在我的测试中,我只是向这个 /test 端点发送请求并期望它已配置(根据您的实现,如果您不扩展 spring 云网关我可以说这个测试没用,我们不应该测试spring云网关功能,但无论如何这是我从你的描述中理解的)
@RunWith(SpringRunner.class)
@SpringBootTest(properties = {
"spring.cloud.gateway.routes[0].id=test",
"spring.cloud.gateway.routes[0].uri=http://localhost:8081",
"spring.cloud.gateway.routes[0].predicates[0]=Path=/test/**",
}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class NettyRoutingFilterTests {
@Autowired
private ApplicationContext context;
@Test
@Ignore
public void mockServerWorks() {
WebTestClient client = WebTestClient.bindToApplicationContext(this.context)
.build();
client.get().uri("/test").exchange().expectStatus().isOk();
}
复杂
第二种方法可能是;将您的模拟路由定位器设置为源代码中的上下文并调用您的服务,断言您的响应。当你出于某种原因需要一些控制时,这与从 SpringBootProperties 设置路由不同(在我的例子中,我们使用的是合同测试,我不会详细介绍),但这里有一些我没有的模拟尝试完整的示例(但在我的项目中使用相同的方法)但它应该为您提供想法和一些起点;
@ExtendWith( { SpringExtension.class } )
@SpringBootTest(classes = { MockConfigurer.class },
webEnvironment = WebEnvironment.RANDOM_PORT )
public class RoutingIT
{
@LocalServerPort
private int port;
你应该像下面这样模拟路由,所以这将 return 我们的 ServiceInstance 在请求时。在下一步中,我们还将把我们的 ServiceInstance 放到上下文中。 (我在这里使用发现客户端,我的路由是从 consul/eureka 编辑的 return,但这里重要的一点是上下文中有 RouteDefinitions。如果您使用的是另一个定位器,请检查 RouteDefinitionLocator 实现并注入相应的基于此路由到您的上下文);
@Configuration
public class MockConfigurer
{
private List<ServiceInstance> services;
public MockConfigurer( List<ServiceInstance> services)
{
this.services= services;
}
@Bean
public DiscoveryClient discoveryClient( )
{
final DiscoveryClient mock = mock( DiscoveryClient.class );
final Map<String, List<ServiceInstance>> clusters =
this.services.stream( ).collect( Collectors.groupingBy( ServiceInstance::getServiceId ) );
given( mock.getServices( ) ).willReturn( new ArrayList<>( clusters.keySet( ) ) );
clusters.forEach( ( clusterId, services ) -> given( mock.getInstances( clusterId ) ).willReturn( services ) );
return mock;
}
}
现在在您的测试中实施 MockService;
public class MockService implements ServiceInstance
{
// fields, constructors
@Override
public String getServiceId( )
{
return id;
}
@Override
public int getPort( )
{
return port;
}
// and other functions as well, but you will get the point
在您的测试中创建此 MockService 的实例并将它们注入 spring 上下文,以便它们可以被发现我们以前的 MockConfigurer 作为服务;
@Bean
public static MockService mockClusterInstance1( )
{
return new MockService("test", 8081, // more fields based on your implementation, also pay attention this is what we defined in the @SpringBootTest annotation);
}
现在一切就绪,可以测试了。
@Test
public void should_GetResponseFromTest_WhenCalled( ) throws Exception
{
URI uri= new URI( "http://localhost:" + this.port+ "/test");
ResponseEntity<String> res = this.restTemplate.getForEntity( uri, String.class );
assertThat( res.getStatusCodeValue( ) ).isEqualTo( HttpURLConnection.HTTP_OK );
assertThat( res.getBody( ) ).isEqualTo( // your expectation );
我想使用 JUnit5 对 Spring Cloud Gateway RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { 方法进行正确的单元测试。
但是,我很难弄清楚要测试什么、断言什么、模拟什么、如何提高覆盖率等等... 如果可能的话,我只想对此进行单元测试,无需开始整个 SpringTest 等
@Bean
@Override
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
return routeLocatorBuilder.routes()
.route("forward_to_service_one", r -> r.path("/serviceone/**").and().uri("http://the-first-service:8080"))
.route("forward_to_service_two", r -> r.path("/servicetwo/**").and().uri("http://the-second-service:8080"))
.route("forward_to_service_three", r -> r.alwaysTrue().and().order(Ordered.LOWEST_PRECEDENCE).uri("http://the-default-third-service:8080"))
.build();
}
在进行集成测试时,点击端点上启动的网关服务,看到转发到各个服务的请求,我想知道是否有测试此 Spring 云网关功能的良好做法.
请提供完全覆盖的测试用例示例?
谢谢
我无法理解你的测试场景(你想测试什么,是否为路径正确配置了服务?)但我想向你展示两种方法,第一种是基本的,第二种如果你需要更多的控制,那就更复杂了。
简单
这很简单,我正在向我的 SpringBootTest 属性添加一些路由,我使用 Spring 提供的 WebTestClient 实用程序来对 Netty 进行反应性测试。然后在我的测试中,我只是向这个 /test 端点发送请求并期望它已配置(根据您的实现,如果您不扩展 spring 云网关我可以说这个测试没用,我们不应该测试spring云网关功能,但无论如何这是我从你的描述中理解的)
@RunWith(SpringRunner.class)
@SpringBootTest(properties = {
"spring.cloud.gateway.routes[0].id=test",
"spring.cloud.gateway.routes[0].uri=http://localhost:8081",
"spring.cloud.gateway.routes[0].predicates[0]=Path=/test/**",
}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class NettyRoutingFilterTests {
@Autowired
private ApplicationContext context;
@Test
@Ignore
public void mockServerWorks() {
WebTestClient client = WebTestClient.bindToApplicationContext(this.context)
.build();
client.get().uri("/test").exchange().expectStatus().isOk();
}
复杂
第二种方法可能是;将您的模拟路由定位器设置为源代码中的上下文并调用您的服务,断言您的响应。当你出于某种原因需要一些控制时,这与从 SpringBootProperties 设置路由不同(在我的例子中,我们使用的是合同测试,我不会详细介绍),但这里有一些我没有的模拟尝试完整的示例(但在我的项目中使用相同的方法)但它应该为您提供想法和一些起点;
@ExtendWith( { SpringExtension.class } )
@SpringBootTest(classes = { MockConfigurer.class },
webEnvironment = WebEnvironment.RANDOM_PORT )
public class RoutingIT
{
@LocalServerPort
private int port;
你应该像下面这样模拟路由,所以这将 return 我们的 ServiceInstance 在请求时。在下一步中,我们还将把我们的 ServiceInstance 放到上下文中。 (我在这里使用发现客户端,我的路由是从 consul/eureka 编辑的 return,但这里重要的一点是上下文中有 RouteDefinitions。如果您使用的是另一个定位器,请检查 RouteDefinitionLocator 实现并注入相应的基于此路由到您的上下文);
@Configuration
public class MockConfigurer
{
private List<ServiceInstance> services;
public MockConfigurer( List<ServiceInstance> services)
{
this.services= services;
}
@Bean
public DiscoveryClient discoveryClient( )
{
final DiscoveryClient mock = mock( DiscoveryClient.class );
final Map<String, List<ServiceInstance>> clusters =
this.services.stream( ).collect( Collectors.groupingBy( ServiceInstance::getServiceId ) );
given( mock.getServices( ) ).willReturn( new ArrayList<>( clusters.keySet( ) ) );
clusters.forEach( ( clusterId, services ) -> given( mock.getInstances( clusterId ) ).willReturn( services ) );
return mock;
}
}
现在在您的测试中实施 MockService;
public class MockService implements ServiceInstance
{
// fields, constructors
@Override
public String getServiceId( )
{
return id;
}
@Override
public int getPort( )
{
return port;
}
// and other functions as well, but you will get the point
在您的测试中创建此 MockService 的实例并将它们注入 spring 上下文,以便它们可以被发现我们以前的 MockConfigurer 作为服务;
@Bean
public static MockService mockClusterInstance1( )
{
return new MockService("test", 8081, // more fields based on your implementation, also pay attention this is what we defined in the @SpringBootTest annotation);
}
现在一切就绪,可以测试了。
@Test
public void should_GetResponseFromTest_WhenCalled( ) throws Exception
{
URI uri= new URI( "http://localhost:" + this.port+ "/test");
ResponseEntity<String> res = this.restTemplate.getForEntity( uri, String.class );
assertThat( res.getStatusCodeValue( ) ).isEqualTo( HttpURLConnection.HTTP_OK );
assertThat( res.getBody( ) ).isEqualTo( // your expectation );