如何使用Spring在单元测试中模拟远程REST API?

33 java rest unit-testing spring-test

假设我在我的应用程序中创建了一个使用远程Web服务的简单客户端,该服务在某个URI上公开RESTful API /foo/bar/{baz}.现在我想对我的客户端进行单元测试,该客户端调用此Web服务.

理想情况下,在我的测试中,我想模拟我从Web服务获得的响应,给出一个特定的请求,如/foo/bar/123/foo/bar/42.我的客户端假设API实际上正在某个地方运行,所以我需要一个本地"Web服务"来开始运行http://localhost:9090/foo/bar我的测试.

我希望我的单元测试是自包含的,类似于使用Spring MVC Test框架测试Spring控制器.

一些简单客户端的伪代码,从远程API获取数字:

// Initialization logic involving setting up mocking of remote API at 
// http://localhost:9090/foo/bar

@Autowired
NumberClient numberClient // calls the API at http://localhost:9090/foo/bar

@Test
public void getNumber42() {
    onRequest(mockAPI.get("/foo/bar/42")).thenRespond("{ \"number\" : 42 }");
    assertEquals(42, numberClient.getNumber(42));
}

// ..
Run Code Online (Sandbox Code Playgroud)

使用Spring有哪些替代方案?

Tun*_*uno 16

如果您使用Spring RestTemplate,则可以使用MockRestServiceServer.可以在这里找到一个例子https://objectpartners.com/2013/01/09/rest-client-testing-with-mockrestserviceserver/

  • 可以肯定的是:我想这只有在测试的客户端使用相同的“RestTemplate”实例时才有效?就我而言,测试的代码在内部实例化自己的“RestTemplate”,但我无权访问它。所以我不能使用`MockRestServiceServer`? (2认同)

小智 13

最好的方法是使用WireMock.添加以下依赖项:

    <dependency>
        <groupId>com.github.tomakehurst</groupId>
        <artifactId>wiremock</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.igniterealtime.smack</groupId>
        <artifactId>smack-core</artifactId>
        <version>4.0.6</version>
    </dependency>
Run Code Online (Sandbox Code Playgroud)

定义并使用线缆如下所示

@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);

String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
            .willReturn(aResponse().withStatus(200)
                    .withHeader("Content-Type", "application/json").withBody(response)));
Run Code Online (Sandbox Code Playgroud)


the*_*dam 7

如果你想对你的客户端进行单元测试,那么你就会模拟出正在进行REST API调用的服务,即使用mockito - 我假设你有一个为你做这些API调用的服务,对吧?

另一方面,如果你想"模拟"其余的API,因为有某种服务器会给你响应,这将更符合集成测试,你可以尝试其中一个像restito那样的框架,休息司机betamax.


nob*_*bar 7

您可以轻松地使用MockitoSpring Boot 中模拟 REST API 。

在您的测试树中放置一个存根控制器:

@RestController
public class OtherApiHooks {

    @PostMapping("/v1/something/{myUUID}")
    public ResponseEntity<Void> handlePost(@PathVariable("myUUID") UUID myUUID ) {
        assert (false); // this function is meant to be mocked, not called
        return new ResponseEntity<Void>(HttpStatus.NOT_IMPLEMENTED);
    }
}
Run Code Online (Sandbox Code Playgroud)

运行测试时,您的客户端需要在本地主机上调用 API 。这可以在src/test/resources/application.properties. 如果测试正在使用RANDOM_PORT,则您的被测客户端将需要找到该值。这有点棘手,但问题在这里得到解决:Spring Boot - How to get the running port

将您的测试类配置为使用WebEnvironment(正在运行的服务器),现在您的测试可以以标准方式使用 Mockito,ResponseEntity根据需要返回对象:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestsWithMockedRestDependencies {

  @MockBean private OtherApiHooks otherApiHooks;

  @Test public void test1() {
    Mockito.doReturn(new ResponseEntity<Void>(HttpStatus.ACCEPTED))
      .when(otherApiHooks).handlePost(any());
    clientFunctionUnderTest(UUID.randomUUID()); // calls REST API internally
    Mockito.verify(otherApiHooks).handlePost(eq(id));
  }

}
Run Code Online (Sandbox Code Playgroud)

您还可以使用它在上面创建的模拟环境中对整个微服务进行端到端测试。一种方法是注入TestRestTemplate您的测试类,并使用它来调用您的REST API,而不是clientFunctionUnderTest从示例中调用。

@Autowired private TestRestTemplate restTemplate;
@LocalServerPort private int localPort; // you're gonna need this too
Run Code Online (Sandbox Code Playgroud)

这是如何工作的

因为OtherApiHooks@RestController在测试树中,Spring Boot 在运行时会自动建立指定的 REST 服务SpringBootTest.WebEnvironment

Mockito 在这里用于模拟控制器类——而不是整个服务。因此,在mock被命中之前会有一些由Spring Boot管理的服务器端处理。这可能包括反序列化(和验证)示例中显示的路径 UUID 之类的事情。

据我所知,这种方法对于 IntelliJ 和 Maven 的并行测试运行是稳健的。