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

后端 未结 6 705
生来不讨喜
生来不讨喜 2020-12-14 00:49

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 uni

相关标签:
6条回答
  • 2020-12-14 01:03

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

    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:

    @Configuration
    public class UserConfig {
    
        @Bean
        public UsersController usersController() {
            return new UsersController();
        }
    
        @Bean
        public UserService userService() {
            return Mockito.mock(UserService.class);
        }
    }
    

    The UserCollectionItemDto is a simple POJO and it represents what the API consumer sends to the server. The UserProfile is the main object used in the service layer (by the UserService class). This behaviour also implements the DTO pattern.

    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.

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

    0 讨论(0)
  • 2020-12-14 01:10

    If you want to unit test 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?

    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-driver or betamax.

    0 讨论(0)
  • 2020-12-14 01:14

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

    0 讨论(0)
  • 2020-12-14 01:14

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

        <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

    @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)));
    
    0 讨论(0)
  • 2020-12-14 01:16

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

    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 localhost when 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

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

    @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 TestRestTemplate into your test class, and use that to call your REST API in place of clientFunctionUnderTest from the example.

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

    How this works

    Because OtherApiHooks is a @RestController in the test tree, Spring Boot will automatically establish the specified REST service when running the 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.

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

    0 讨论(0)
  • 2020-12-14 01:16

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

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

    Hope this helps,

    Sam

    0 讨论(0)
提交回复
热议问题