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

105,669

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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

Share:
105,669

Related videos on Youtube

Admin
Author by

Admin

Updated on July 09, 2022

Comments

  • Admin
    Admin almost 2 years

    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.

    Ideally, in my tests, I’d like to mock the responses I get from the web service, given a specific request like /foo/bar/123 or /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/bar for my tests.

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

    Some pseudo-code for a simple client, fetching numbers from the remote 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?

    • Manu
      Manu over 9 years
      Do you leverage the DTO pattern?
    • Admin
      Admin over 9 years
      @Manu Do you mean if I marshall to and from JSON into domain objects in my client?
    • Manu
      Manu over 9 years
      More precisely, if you marshal/unmarshal into intermediate objects representing your domain entities in the network
    • Admin
      Admin over 9 years
      @Manu No, I don’t have intermediate objects, only domain objects and JSON text.
    • Manu
      Manu over 9 years
      Then I'd recommend start using the DTO pattern and proceed the same way you did for testing MVC controllers.
    • chrylis -cautiouslyoptimistic-
      chrylis -cautiouslyoptimistic- over 9 years
      If you're already okay with Spring, the obvious choice is to use (and mock) RestTemplate.
    • Admin
      Admin over 9 years
      @Manu Okay, could you explain that a little bit more? Give an example in an answer?
    • Manu
      Manu over 9 years
      If you are using rest and leveraging the DTO pattern, then I'd recommend you follow this tutorial.
  • Tuno
    Tuno over 7 years
    If you don't use Spring RestTemplate, you can check WireMock. However this will start a server in the background.
  • powder366
    powder366 about 7 years
    Where is aResponse() coming from?
  • best wishes
    best wishes over 6 years
    @powder366 WireMock.aResponse()
  • Brent Bradburn
    Brent Bradburn almost 6 years
    WARNING: Note that assert may be out-of-favor with some reviewers.
  • shellbye
    shellbye about 5 years
    This one is newer.
  • Lakshman
    Lakshman over 4 years
    Thanks for the answer, I was struggling with mocking remote API finally I have a solution.
  • Juh_
    Juh_ over 4 years
    To be sure: I guess this only work if the tested client use the same instance of RestTemplate? In my case, the tested code instantiate its own RestTemplate internally and I don't have access to it. So I can't use MockRestServiceServer?
  • Daniel Pop
    Daniel Pop almost 4 years
    this is simply AWESOME
  • Christian Vincenzo Traina
    Christian Vincenzo Traina almost 2 years
    Too bad WireMock requires Jitpack, and our IT Risk office forbids us to build it in production servers since it would introduce a vulnerability. For this reason, even if I like WireMock and I used it with success, I'm still looking for alternatives
  • Christian Vincenzo Traina
    Christian Vincenzo Traina almost 2 years
    Solutions like WireMock allow you to copy/paste a serialized response and test how it gets deserialized by the code. On the other hand, when mocking the RestTemplate you are passing an already existent ResponseEntity, which makes the test way less powerful. As an example, WireMock would let you test different Content-Type response headers and let you see how they are handled