如何在JUnit测试中将MockWebServer端口设置为WebClient?

问题描述 投票:0回答:3

我将

spring-boot
WebClient
一起使用,它作为 bean 自动装配。

问题:在编写

junit
集成测试时,我必须使用okhttp
MockWebServer
。该模拟始终在随机端口上启动,例如
localhost:14321

现在我的

WebClient
当然有一个固定的 url 来发送请求。该 url 可能由
application.properties
参数(如
webclient.url=https://my.domain.com/
)给出,因此我可以在
junit
测试中覆盖该字段。但只是静态的。

问题:如何重置

WebClient
中的
@SpringBootTest
bean,以便它始终将请求发送到我的模拟服务器?

@Service
public class WebClientService {
     public WebClientService(WebClient.Builder builder, @Value("${webclient.url}" String url) {
          this.webClient = builder.baseUrl(url)...build();
     }

     public Response send() {
          return webClient.body().exchange().bodyToMono();
     }
}

@Service
public void CallingService {
      @Autowired
      private WebClientService service;

      public void call() {
           service.send();
      }
}


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyWebTest {
        @Autowired
        private CallingService calling;

        @Test
        public void test() {
             MockWebServer mockWebServer = new MockWebServer();
             System.out.println("Current mock server url: " + mockWebServer.url("/").toString()); //this is random    

             mockWebServer.enqueue(new MockResponse()....);

             //TODO how to make the mocked server url public to the WebClient?
             calling.call();
        }
}

如您所见,我正在编写完整的现实世界 junit 集成测试。唯一的问题是:如何将

MockWebServer
url 和端口传递到
WebClient
,以便它自动将请求发送到我的模拟??

旁注:我绝对需要这里的

MockWebServer
随机端口,以免干扰其他正在运行的测试或应用程序。因此必须坚持使用随机端口,并找到一种将其传递到 Web 客户端的方法(或动态覆盖应用程序属性)。


更新: 我想出了以下有效的方法。但也许有人知道如何使模拟服务器字段非静态

@ContextConfiguration(initializers = RandomPortInitializer.class)
public abstract class AbstractITest {
    @ClassRule
    public static final MockWebServer mockWebServer = new MockWebServer();

    public static class RandomPortInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
                    "webclient.url=" + mockWebServer.url("/").toString());
        }
    }
}
java spring spring-boot okhttp mockwebserver
3个回答
21
投票

从 Spring Framework 5.2.5 (Spring Boot 2.x) 开始,您可以使用

DynamicPropertySource
注释,这非常方便。

这是一个完整的示例,如何将它与

MockWebServer
一起使用来绑定正确的端口:

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public abstract class AbstractIT {

    static MockWebServer mockWebServer;

    @DynamicPropertySource
    static void properties(DynamicPropertyRegistry r) throws IOException {
        r.add("some-service.url", () -> "http://localhost:" + mockWebServer.getPort());
    }

    @BeforeAll
    static void beforeAll() throws IOException {
        mockWebServer = new MockWebServer();
        mockWebServer.start();
    }

    @AfterAll
    static void afterAll() throws IOException {
        mockWebServer.shutdown();
    }
}

1
投票

这是您如何使用 MockWebServer 的 URL 设置 WebClient 基本 URL。

WebClient 和 MockWebServer 足够轻,可以为每个测试重新创建。

@SpringBootTest
class Tests {

  @Autowired
  private WebClient.Builder webClientBuilder;

  private MockWebServer mockWebServer;

  private WebClient client;

  @BeforeEach
  void setUp() throws Exception {
    mockWebServer = new MockWebServer();
    mockWebServer.start();

    // Set WebClinet base URL with the the mock web server URL 
    webClientBuilder.baseUrl(mockWebServer.url("").toString());

    client = webClientBuilder.build();

  }

  @AfterEach
  void tearDown() throws Exception {
    mockWebServer.shutdown();
  }

  @Test
  void bodyIsExpected() throws InterruptedException {

    mockWebServer.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK").setBody("Hello World!")
        .addHeader("Content-Type", "text/plain"));

    ResponseEntity<String> response = client.get().retrieve().toEntity(String.class).block();

    assertThat(response.getBody(), is("Hello World!"));
  }
}

0
投票

这是我正在使用的解决方案。使用 SpringBootTest 的 Spring Boot 2 和 Spring-Webflux 5.3.30

  1. MyServiceClientTestConfig 创建注入的 beans 端口和 myServiceClient,其中

    • 端口 Integer bean 是使用 Spring 的 TestSocketUtils.findAvailableTcpPort() 生成的
    • MyServiceClient 是一个 Spring 服务,它使用 WebClient 并使用基本 URL 进行初始化。
  2. 如文档所述,我们不应该在测试之间重复使用 MockWebServer 实例,因此在每次测试之前使用我们在测试初始化时发现的空闲端口创建它,并在每次测试后将其关闭。

我想有一个很小的机会,因为我在 Spring 初始化应用程序上下文时找到了该端口,并且在第一次调用 init 之前和测试之间有很短的一段时间内 MockWebServer 没有使用该端口,所以另一个进程可能会占用端口,但在测试生命周期内发生这种情况的可能性似乎非常小。

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyServiceClientTest {

    private static final Logger LOG = LoggerFactory.getLogger(MyServiceClientTest.class);

    private MockWebServer mockWebServer;

    @Autowired
    private MyServiceClient myServiceClient;

    @Autowired
    private Integer port;

    @BeforeEach
    void init() throws IOException {
        assertNotNull(myServiceClient);
        assertNotNull(port);

        // MockWebServer not re-usable between tests
        mockWebServer = new MockWebServer();
        mockWebServer.start(port);

        assertNotNull(mockWebServer);
        assertEquals(port, mockWebServer.getPort());
    }

    @AfterEach
    void cleanUp() throws IOException {
        // MockWebServer not re-usable between tests
        mockWebServer.shutdown();
    }

    @Test
    public void placeholderTest1() {
        LOG.info("Hello 1 {}", mockWebServer.getPort());
    }

    @Test
    public void placeholderTest2() {
        LOG.info("Hello 2 {}", mockWebServer.getPort());
    }

    @Configuration
    static class MyServiceClientTestConfig {

        @Bean
        Integer getFreePort () {
            // find a free port to use for this test
            return TestSocketUtils.findAvailableTcpPort();
        }

        @Bean
        MyServiceClient getMyServiceClient(Integer port) {
            return new MyServiceClient(String.format("http://localhost:%d", port));
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.