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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25564533/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 00:40:15  来源:igfitidea点击:

How to mock remote REST API in unit test with Spring?

javaunit-testingrestspring-test

提问by

Assume I have made a simple client in my application that uses a remote web service that is exposing a RESTful API at some URI /foo/bar/{baz}. Now I wish to unit test my client that makes calls to this web service.

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

Ideally, in my tests, I'd like to mock the responses I get from the web service, given a specific request like /foo/bar/123or /foo/bar/42. My client assumes the API is actually running somewhere, so I need a local "web service" to start running on http://localhost:9090/foo/barfor my tests.

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

I want my unit tests to be self-contained, similar to testing Spring controllers with the Spring MVC Test framework.

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

Some pseudo-code for a simple client, fetching numbers from the remote API:

一些简单客户端的伪代码,从远程 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));
}

// ..

What are my alternatives using Spring?

我使用 Spring 的替代方案是什么?

回答by theadam

If you want to unittest your client, then you'd mock out the services that are making the REST API calls, i.e. with mockito- I assume you do have a service that is making those API calls for you, right?

如果您想对您的客户端进行单元测试,那么您可以模拟进行 REST API 调用的服务,即使用mockito- 我假设您确实有一个为您进行这些 API 调用的服务,对吧?

If on the other hand you want to "mock out" the rest APIs in that there is some sort of server giving you responses, which would be more in line of integration testing, you could try one of the many framework out there like restito, rest-driveror betamax.

另一方面,如果您想“模拟”其余的 API,因为有某种服务器为您提供响应,这将更符合集成测试,您可以尝试使用许多框架之一,例如restito休息驱动程序betamax

回答by Sam Brannen

What you are looking for is the support for Client-side REST Testsin the Spring MVC Test Framework.

您正在寻找的是Spring MVC 测试框架中对客户端 REST 测试的支持。

Assuming your NumberClientuses Spring's RestTemplate, this aforementioned support is the way to go!

假设您NumberClient使用 Spring 的RestTemplate,上述支持就是要走的路!

Hope this helps,

希望这可以帮助,

Sam

山姆

回答by Tuno

If you use Spring RestTemplateyou can use MockRestServiceServer. An example can be found here REST Client Testing With MockRestServiceServer.

如果您使用 Spring RestTemplate,则可以使用MockRestServiceServer. 可以在此处找到示例REST 客户端测试使用MockRestServiceServer.

回答by Yallaling Goudar

Best method is to use WireMock. Add the following dependencies:

最好的方法是使用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>

Define and use the wiremock as shown below

定义和使用wiremock,如下所示

@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)));

回答by Menelaos Kotsollaris

Here is a basic example on how to mock a Controller class with Mockito:

这是一个关于如何使用Mockito模拟 Controller 类的基本示例:

The Controller class:

控制器类:

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private UserService userService;

    public Page<UserCollectionItemDto> getUsers(Pageable pageable) {
        Page<UserProfile> page = userService.getAllUsers(pageable);
        List<UserCollectionItemDto> items = mapper.asUserCollectionItems(page.getContent());
        return new PageImpl<UserCollectionItemDto>(items, pageable, page.getTotalElements());
    }
}

Configure the beans:

配置bean:

@Configuration
public class UserConfig {

    @Bean
    public UsersController usersController() {
        return new UsersController();
    }

    @Bean
    public UserService userService() {
        return Mockito.mock(UserService.class);
    }
}

The UserCollectionItemDtois a simple POJOand it represents what the API consumer sends to the server. The UserProfile is the main object used in the service layer (by the UserServiceclass). This behaviour also implements the DTO pattern.

UserCollectionItemDto是一个简单的POJO,它代表 API 使用者发送到服务器的内容。UserProfile 是服务层中使用的主要对象(由UserService类)。此行为也实现了DTO 模式

Finally, mockup the expected behaviour:

最后,模拟预期的行为:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
@Import(UserConfig.class)
public class UsersControllerTest {

    @Autowired
    private UsersController usersController;

    @Autowired
    private UserService userService;

    @Test
    public void getAllUsers() {
        initGetAllUsersRules();
        PageRequest pageable = new PageRequest(0, 10);
        Page<UserDto> page = usersController.getUsers(pageable);
        assertTrue(page.getNumberOfElements() == 1);
    }

    private void initGetAllUsersRules() {
        Page<UserProfile> page = initPage();
        when(userService.getAllUsers(any(Pageable.class))).thenReturn(page);
    }

    private Page<UserProfile> initPage() {
        PageRequest pageRequest = new PageRequest(0, 10);
        PageImpl<UserProfile> page = new PageImpl<>(getUsersList(), pageRequest, 1);
        return page;
    }

    private List<UserProfile> getUsersList() {
        UserProfile userProfile = new UserProfile();
        List<UserProfile> userProfiles = new ArrayList<>();
        userProfiles.add(userProfile);
        return userProfiles;
    }
}

The idea is to use the pure Controller bean and mockup its members. In this example, we mocked the UserService.getUsers()object to contain a user and then validated whether the Controller would return the right number of users.

这个想法是使用纯控制器 bean 并模拟其成员。在这个例子中,我们模拟UserService.getUsers()对象包含一个用户,然后验证控制器是否会返回正确数量的用户。

With the same logic you can test the Service and other levels of your application. This example uses the Controller-Service-Repository Patternas well :)

使用相同的逻辑,您可以测试服务和应用程序的其他级别。这个例子也使用了Controller-Service-Repository 模式:)

回答by nobar

You can easily use Mockitoto mock a REST API in Spring Boot.

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

Put a stubbed controller in your test tree:

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

@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);
    }
}

Your client will need to call the API on localhostwhen running tests. This could be configured in src/test/resources/application.properties. If the test is using RANDOM_PORT, your client under test will need to find that value. This is a bit tricky, but the issue is addressed here: Spring Boot - How to get the running port

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

Configure your test class to use a WebEnvironment(a running server) and now your test can use Mockito in the standard way, returning ResponseEntityobjects as needed:

将您的测试类配置为使用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));
  }

}

You can also use this for end-to-end testing of your entire microservice in an environment with the mock created above. One way to do this is to inject TestRestTemplateinto your test class, and use that to call yourREST API in place of clientFunctionUnderTestfrom the example.

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

@Autowired private TestRestTemplate restTemplate;
@LocalServerPort private int localPort; // you're gonna need this too


How this works

这是如何工作的

Because OtherApiHooksis a @RestControllerin the test tree, Spring Boot will automatically establish the specified REST service when running the SpringBootTest.WebEnvironment.

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

Mockito is used here to mock the controller class -- not the service as a whole. Therefore, there will be some server-side processing managed by Spring Boot before the mock is hit. This may include such things as deserializing (and validating) the path UUID shown in the example.

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

From what I can tell, this approach is robust for parallel test runs with IntelliJ and Maven.

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