In the previous lab, we separated the fortune service from the greeting application. In the process, we created two new classes:

  • On one side (the producer), the FortuneController exposes fortunes over HTTP

  • On the consumer side, the FortuneServiceClient consumes fortunes by making REST API calls

Our projects currently have only unit tests.

Run the unit tests for fortune-service, and then for greeting-app, and make sure they pass.

FortuneController and FortuneServiceClient currently have no tests. Let’s rectify that.

1. Testing FortuneController

With its Spring MVC Test Framework, Spring provides support for testing controllers through the Servlet API call chain.

The idea for the test is to call the fortune controller more like a black box, through its HTTP interface, and then verify that we get the response we expect. Such a test constructs the entire Spring application context, and so exercises a larger portion of our application.

The fortune controller has only one collaborator, the fortuneService, which can be easily mocked. Let’s take a look..

package io.pivotal.training.fortune;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class) (1)
@WebMvcTest(FortuneController.class) (2)
public class FortuneControllerTests {

  @Autowired private MockMvc mockMvc;

  @MockBean private FortuneService fortuneService; (3)

  private static final String ExpectedFortune = "my special fortune";

  @Before
  public void setup() {
    when(fortuneService.getFortune()).thenReturn(ExpectedFortune); (4)
  }

  @Test
  public void shouldGetFortuneOverHttp() throws Exception { (5)
    mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
        .andExpect(jsonPath("$.fortune").value(ExpectedFortune));

    verify(fortuneService).getFortune();
  }
}
1 Customize the JUnit Runner so that the Spring testing framework has a chance to setup the test
2 Instruct the test framework to test a Spring MVC slice
3 The @MockBean annotation not only provides a mock for the fortune service, but also injects it as a Spring bean into the fortune controller
4 Configure the mock to return a fortune we can use later to assert that our http call returned that fortune
5 Make the HTTP call, and verify: Did we get an HTTP 200 response? Does the response contain the right json content? Was the fortune service called as a consequence of invoking this HTTP request?

Write (or copy) the above test to the fortune-service project, run it, make sure it passes.

Next, let’s turn our attention to the greeting application..

1.1. Testing FortuneServiceClient

The idea here is to ask the fortune service client for a fortune, and make sure we get back what we expected. Here’s a simple test implementation:

package io.pivotal.training.greeting;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest
public class FortuneServiceClientTests {

  @Autowired private FortuneServiceClient fortuneServiceClient;

  @Test
  public void shouldReturnAFortune() {
    assertThat(fortuneServiceClient.getFortune()).isNotBlank();
  }

}

Here we focus on how the greeting application calls out to the fortune service, and how it consumes that information.

Since our fortune service is not running, the test should fail with a network exception:

ResourceAccessException: I/O error on GET request for "http://localhost:8081": Connection refused (Connection refused)

Start an instance of the fortune service, re-run the test, and verify that this time it passes.

The problem here is that we don’t want to have to stand up the fortune-service for this test. We know the contract, and we wish to test how the greeting application is making the call and consuming the results.

For that, we need a stub. The wiremock project was designed for just this purpose. It’s a library for dynamically constructing stubs of http apis, allowing us to control exactly its behavior before running a test.

If you’re not familiar with wiremock, this is the time to take a look at their getting started document.

2. Use wiremock to stub the fortune service

Add the wiremock standalone library to the greeting application’s build file as a test dependency:

testCompile 'com.github.tomakehurst:wiremock-standalone:2.14.0'

If necessary, update your IDE configuration to match the updated gradle build file.

Next, let’s turn our attention to how to modify our test to use wiremock:

  1. Add a wiremock rule, configured to listen on the same port that the fortune-service is configured for: 8081

  2. Add a @Before method for the test, and in it, use the wiremock dsl to configure the stub to return some fortune

  3. In the test, consider also adding a verification that the stub endpoint was called

Here’s a working version of the retrofitted test class:

package io.pivotal.training.greeting;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest
public class FortuneServiceClientTests {

  @Autowired private FortuneServiceClient fortuneServiceClient;

  @Rule (1)
  public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8081));

  private static final String ExpectedFortune = "some fortune";

  @Before
  public void setup() { (2)
    stubFor(WireMock.get(urlEqualTo("/"))
        .willReturn(aResponse()
            .withStatus(200)
            .withHeader("Content-Type", "application/json")
            .withBody(String.format("{ \"fortune\": \"%s\" }", ExpectedFortune))
        ));
  }

  @Test
  public void shouldReturnAFortune() {
    assertThat(fortuneServiceClient.getFortune()).isEqualTo(ExpectedFortune);
  }

}
1 The wiremock rule is added and configured to listen on port 8081
2 Configure the stub to return 200 (success), content type json, and some hard-coded fortune

We take care to start the stub before the test begins, so it can make its call and evaluate the results.

Verify that your fortune service client test is now passing, without the need to have a running instance of the fortune service.

3. Congratulations

At this point, we feel pretty good that we can test our services in isolation, with a strategy of using stubs in place of other microservices, so that we don’t have to "stand up the world" in order to run through our integration tests.

We’ve made progress, but not all is well. Next up, let’s explore the rationale behind consumer-driven contracts, and specifically how the Spring Cloud Contract project assists in solving a problem that arises when we break a monolithic application into multiple independent services.