如何对spring的网关进行单元测试?

pio*_*rek 4 java testing spring spring-test spring-cloud-gateway

我的网关会将流量重定向到许多不同的服务(在不同的域名下)。如何测试网关的配置?只需一项服务,我就可以设置模拟服务器(如 httpbin)并测试响应。对于多个服务,我宁愿避免启动整个 docker 网络或更改 locak dns 别名。spring 是否提供了任何轻量级的测试网关的方法?

api*_*sim 5

以下是如何使用API 模拟器实现您想要的效果:

package my.package;

import static com.apisimulator.embedded.SuchThat.isEqualTo;
import static com.apisimulator.embedded.SuchThat.startsWith;
import static com.apisimulator.embedded.http.HttpApiSimulation.httpApiSimulation;
import static com.apisimulator.embedded.http.HttpApiSimulation.httpRequest;
import static com.apisimulator.embedded.http.HttpApiSimulation.httpResponse;
import static com.apisimulator.embedded.http.HttpApiSimulation.simlet;
import static com.apisimulator.http.Http1Header.CONTENT_TYPE;
import static com.apisimulator.http.HttpMethod.CONNECT;
import static com.apisimulator.http.HttpMethod.GET;
import static com.apisimulator.http.HttpStatus.OK;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;

import java.time.Duration;
import java.util.Map;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.SocketUtils;

import com.apisimulator.embedded.http.JUnitHttpApiSimulation;

@RunWith(SpringRunner.class)
@SpringBootTest(
   webEnvironment = RANDOM_PORT,
   properties = { 
      "management.server.port=${test.port}", "logging.level.root=info",
      // Configure the Gateway to use HTTP proxy - the API Simulator 
      // instance running at localhost:6090
      "spring.cloud.gateway.httpclient.proxy.host=localhost",
      "spring.cloud.gateway.httpclient.proxy.port=6090"
      //"logging.level.reactor.netty.http.server=debug",
      //"spring.cloud.gateway.httpserver.wiretap=true" 
   }
)
@Import(ServiceGatewayApplication.class)
public class ServiceGatewayApplicationTest
{

   // Configure an API simulation. This starts up an instance 
   // of API Simulator on localhost, default port 6090
   @ClassRule
   public static final JUnitHttpApiSimulation clApiSimulation = JUnitHttpApiSimulation
            .as(httpApiSimulation("svc-gateway-backends"));

   protected static int managementPort;

   @LocalServerPort
   protected int port = 0;

   protected String baseUri;
   protected WebTestClient webClient;

   @BeforeClass
   public static void beforeClass()
   {
      managementPort = SocketUtils.findAvailableTcpPort();
      System.setProperty("test.port", String.valueOf(managementPort));

      // Configure simlets for the API simulation
      // @formatter:off
      clApiSimulation.add(simlet("http-proxy")
         .when(httpRequest(CONNECT))
         .then(httpResponse(OK))
      );

      clApiSimulation.add(simlet("test-domain-1")
         .when(httpRequest()
               .whereMethod(GET)
               .whereUriPath(isEqualTo("/static"))
               // The `host` header is used to determine the actual destination 
               .whereHeader("host", startsWith("domain-1.com"))
          )
         .then(httpResponse()
               .withStatus(OK)
               .withHeader(CONTENT_TYPE, "application/text")
               .withBody("{ \"domain\": \"1\" }")
          )
      );

      clApiSimulation.add(simlet("test-domain-2")
         .when(httpRequest()
               .whereMethod(GET)
               .whereUriPath(isEqualTo("/v1/api/foo"))
               .whereHeader("host", startsWith("domain-2.com"))
          )
         .then(httpResponse()
               .withStatus(OK)
               .withHeader(CONTENT_TYPE, "application/json; charset=UTF-8")
               .withBody(
                  "{\n" +
                  "   \"domain\": \"2\"\n" + 
                  "}"
                )
          )
      );
      // @formatter:on
   }

   @AfterClass
   public static void afterClass()
   {
      System.clearProperty("test.port");
   }

   @Before
   public void setup()
   {
      // @formatter:off
      baseUri = "http://localhost:" + port;
      webClient = WebTestClient.bindToServer()
         .baseUrl(baseUri)
         .responseTimeout(Duration.ofSeconds(2))
         .build();
      // @formatter:on
   }

   @Test
   public void test_domain1()
   {
      // @formatter:off
      webClient.get()
         .uri("/static")
         .exchange()
         .expectStatus().isOk()
         .expectBody(String.class).consumeWith(result -> 
             assertThat(result.getResponseBody()).isEqualTo("{ \"domain\": \"1\" }")
          );
      // @formatter:on
   }

   @Test
   public void test_domain2()
   {
      // @formatter:off
      webClient.get()
         .uri("/v1/api/foo")
         .exchange()
         .expectStatus().isOk()
         .expectHeader()
            .contentType("application/json; charset=UTF-8")
         .expectBody(Map.class).consumeWith(result -> 
             assertThat(result.getResponseBody()).containsEntry("domain", "2")
          );
      // @formatter:on
   }

}
Run Code Online (Sandbox Code Playgroud)

大部分代码都是基于这个GatewaySampleApplicationTestsSpring Cloud Gateway 项目中的

上面假设网关具有与这些类似的路由(仅限片段):

    ...
    uri: "http://domain-1.com"
    predicates:
      - Path=/static
    ...
    uri: "http://domain-2.com"
    predicates:
      - Path=/v1/api/foo
    ...
Run Code Online (Sandbox Code Playgroud)