de.skuzzle.springboot.test:spring-boot-wiremock

Easily set up WireMock in your SpringBoot tests


License
MIT

Documentation

Maven Central JavaDoc Coverage Status Twitter Follow

spring-boot-wiremock

This is not an official extension from the Spring Team! (Though one exists as part of the spring-cloud project).

The easiest way to setup a WireMock server in your Spring-Boot tests.

  • Run WireMock server on random port
  • Inject WireMock hosts (http and https) as spring application property
  • Easily setup server- and client side SSL
  • Declarative stubs using annotations
<dependency>
    <groupId>de.skuzzle.springboot.test</groupId>
    <artifactId>spring-boot-wiremock</artifactId>
    <version>0.0.18</version>
    <scope>test</scope>
</dependency>
testImplementation 'de.skuzzle.springboot.test:spring-boot-wiremock:0.0.18'

Quick start

All you need to do is to add the @WithWiremock annotation to your Spring-Boot test. The annotation has some configuration options but the most notable one is injectHttpHostInto.

@SpringBootTest
@WithWiremock(injectHttpHostInto = "your.application.serviceUrl")
public class WiremockTest {

    @Value("${your.application.serviceUrl}")
    private String serviceUrl;
    @Autowired
    private WireMockServer wiremock;

    @Test
    void testWithExplicitStub() throws Exception {
        // Use standard WireMock API for minimum coupling to this library
        wiremock.stubFor(WireMock.get("/")
                .willReturn(aResponse().withStatus(200)));

        final ResponseEntity<Object> response = new RestTemplate()
                .getForEntity(serviceUrl, Object.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    }

    @Test
    @HttpStub(
        onRequest = @Request(withMethod = "GET"), 
        respond = @Response(withStatus = HttpStatus.OK)
    )
    void testWithAnnotationStub() throws Exception {
        // Make full use of this library by defining stubs using annotations

        final ResponseEntity<Object> response = new RestTemplate()
                .getForEntity(serviceUrl, Object.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    }
}

Injecting the host into the application context happens before any bean instances are created and the injected property values takes precedence over any other, for example statically configured value. This means, in most cases the extension works out of the box with your current context configuration.

You can see more annotation stubbing examples in this test class.

Rationale

WireMock is an awesome library for mocking HTTP endpoints in unit tests. However, it can be quite cumbersome to integrate with Spring-Boot: when you manually manage the WireMockServer from within the test, there is hardly a chance to use its random base url during Bean creation. That often results in the weirdest stunts of Spring context configuration in order to somehow inject the mock location. For example, your client under test might use the RestTemplate and you decide to make it a mutable field in order to replace it in your test with an instance that knows the WireMock's location.

In a perfect world, you would not need to touch your existing context configuration for just injecting a mock. Consider the @MockBean annotation that allows to simply replace an already configured Bean with a mock. This works without a hassle and involves no stunts like defining a new Bean with same type and @Primary annotation or manually replacing an injected instance using a setter.

The @WithWiremock annotation works just like that: It sets up a WireMock server early enough, so that its base url can be injected into the Spring application properties, simply replacing an existing value.

Compatibility

  • Requires Java 11
  • Tested against Spring-Boot 2.2.13.RELEASE, 2.3.12.RELEASE, 2.4.11, 2.5.5
  • Tested against WireMock 2.27.2

Usage

WireMock based stubbing

If you set up WireMock using @WithWireMock the server instance is made available as bean in the Spring ApplicationContext. It can thus be injected into the test class like this:

@WithWiremock(...)
@SpringBootTest
public class WiremockTest {

    @Autowired
    private WireMockServer wiremock;
}

You can then use the normal WireMock API in your test methods to define stubs and verifications.

Annotation based stubbing

If you opt-in to use annotation based stubbing provided by this library you gain the advantages of full declarative stubbing and easily reusable stubs.

Warning: Please note that using annotation based stubbing will make it harder to get rid of this library from your code base in the future. You should consider to only use WireMock based stubbing to reduce coupling to this library.

Not all WireMock features (e.g. verifications) are available in annotation based stubbing. It is always possible though to combine annotation based stubs with plain WireMock based stubs as describe above.

Simple stubs

You can define a simple stub by annotating your test/test class with @HttpStub. If you specify no further attributes, the mock will now respond with 200 OK for every request it receives. Note that all additional attributes are optional.

Here is a more sophisticated stub example:

@HttpStub(
    onRequest = @Request(
            withMethod = "POST",
            toUrlPath = "/endpoint",
            withQueryParameters = "param=matching:[a-z]+",
            containingHeaders = "Request-Header=eq:value",
            containingCookies = "sessionId=containing:123456",
            withBody = "containing:Just a body",
            authenticatedBy = @Auth(
                    basicAuthUsername = "username",
                    basicAuthPassword = "password")),
    respond = @Response(
            withStatus = HttpStatus.CREATED,
            withBody = "Hello World",
            withContentType = "application/text",
            withHeaders = "Response-Header=value"))

String matching

All stub request attributes that expect a String value optionally take a matcher prefix like shown in the above example. The following prefixes are supported:

Prefix Operation
eq: Comparison using String.equals
eqIgnoreCase: Comparison using String.equalsIgnoreCase
eqToJson: Interpretes the strings as json
eqToXml Interpretes the strings as xml
matching: Comparison using the provided regex pattern
notMatching: Comparison using the provided regex pattern but negates the result
matchingJsonPath: Interpretes the string as json and matches it against the provided json path
matchingXPath: Interpretes the string as xml and matches it against the provided xpath
containing: Comparison using String.contains

No prefix always results in a comparison using String.equals.

Multiple responses

It is possible to define multiple responses that will be returned by the stub when a stub is matched by consecutive requests. Internally this feature will create a WireMock scenario, thus you can not combine multiple responses and explicit scenario creation using Request.scenario.

@HttpStub(
    respond = {
            @Response(withStatus = HttpStatus.CREATED),
            @Response(withStatus = HttpStatus.OK),
            @Response(withStatus = HttpStatus.ACCEPTED)
    })

When stubbing multiple responses you can define what happens when the last response has been returned using HttpStub.onLastResponse with the following options:

onLastResponse Behavior
WrapAround.RETURN_ERROR Default behavior. Mock will answer with a 403 code after the last stubbed response
WrapAround.START_OVER After the last response the mock will start over and answer with the first stubbed response
WrapAround.REPEAT The mock keeps returning the last stubbed response
@HttpStub(
    // ...
    respond = {
        // ...
    },
    onLastResponse = WrapAround.REPEAT;
)

Sharing stubs

It is possible to share stubs among multiple tests. You can either define your stubs on a super class or an interface implemented by your test class. However, the preferred way of sharing stubs is to create a new annotation which is meta-annotated with all the stubs (and optionally also with @WithWiremock) like in the following example:

@Retention(RUNTIME)
@Target(TYPE)
@WithWiremock(injectHttpHostInto = "sample-service.url")
@HttpStub(onRequest = @Request(toUrl = "/info"),
        respond = @Response(withStatus = HttpStatus.OK, withStatusMessage = "Everything is Ok"))
@HttpStub(onRequest = @Request(toUrl = "/submit/entity", withMethod = "PUT"), respond = {
        @Response(withStatus = HttpStatus.CREATED, withStatusMessage = "Entity created"),
        @Response(withStatus = HttpStatus.OK, withStatusMessage = "Entity already exists")
})
public @interface WithSampleServiceMock {

}

You can now easily reuse the complete mock definition in any SpringBootTest:

@SpringBootTest
@WithSampleServiceMock
public class MetaAnnotatedTest {

    @Value("${sample-service.url}")
    private String sampleServiceUrl;
    
    // ...
}