DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Ensuring API Resilience in Spring Microservices Using Retry and Fallback Mechanisms
  • Consumer-Driven Contract Testing With Spring Cloud Contract
  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Building Mancala Game in Microservices Using Spring Boot (Part 2: Mancala API Implementation)

Trending

  • A Modern Stack for Building Scalable Systems
  • Streamlining Event Data in Event-Driven Ansible
  • GDPR Compliance With .NET: Securing Data the Right Way
  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. WireMock: The Ridiculously Easy Way (For Spring Microservices)

WireMock: The Ridiculously Easy Way (For Spring Microservices)

Creating WireMock stubs requires extra effort; @GenerateWireMockStub for Spring REST controllers makes the creation of WireMock stubs for tests safe and effortless.

By 
Lukasz Gryzbon user avatar
Lukasz Gryzbon
·
Apr. 27, 23 · Tutorial
Likes (9)
Comment
Save
Tweet
Share
10.5K Views

Join the DZone community and get the full member experience.

Join For Free

Using WireMock for integration testing of Spring-based (micro)services can be hugely valuable. However, usually, it requires significant effort to write and maintain the stubs needed for WireMock to take a real service’s place in tests.

What if generating WireMock stubs was as easy as adding @GenerateWireMockStub to your controller? Like this:

Kotlin
 
@GenerateWireMockStub
@RestController
class MyController {
    @GetMapping("/resource")
    fun getData() = MyServerResponse(id = "someId", message = "message")
}


What if that meant that you then just instantiate your producer’s controller stub in consumer-side tests…

Kotlin
 
val myControllerStub = MyControllerStub()


Stub the response…

Kotlin
 
myControllerStub.getData(MyServerResponse("id", "message"))


And verify calls to it with no extra effort?

Kotlin
 
myControllerStub.verifyGetData()


Surely, it couldn’t be that easy?!

Before I explain the framework that does this, let’s first look at the various approaches to creating WireMock stubs.

The Standard Approach

While working on a number of projects, I observed that the writing of WireMock stubs most commonly happens on the consumer side. What I mean by this is that the project that consumes the API contains the stub setup code required to run tests.

Client-side maintained stubs

The benefit of it is that it's easy to implement. There is nothing else the consuming project needs to do. Just import the stubs into the WireMock server in tests, and the job is done.

However, there are also some significant downsides to this approach.

For example, what if the API changes? What if the resource mapping changes? In most cases, the tests for the service will still pass, and the project may get deployed only to fail to actually use the API — hopefully during the build’s automated integration or end-to-end tests. Limited visibility of the API can lead to incomplete stub definitions as well.  

Out of sync client-side maintained stubs

Another downside of this approach is the duplicated maintenance effort — in the worst-case scenario. Each client ends up updating the same stub definitions.

Leakage of the API-specific information, in particular, sensitive information from the producer to the consumer, leads to the consumers being aware of the API characteristics they shouldn’t be. For example, the endpoint mappings or, sometimes even worse — API security keys.                              

Maintaining stubs on the client side can also lead to increased test setup complexity.

The Less Common Approach

A more sophisticated approach that addresses some of the above disadvantages is to make the producer of the API responsible for providing the stubs.

So, how does it work when the stubs live on the producer side?

In a poly-repo environment, where each microservice has its own repository,  this means the producer generates an artifact containing the stubs and publishes it to a common repository (e.g., Nexus) so that the clients can import it and use it. In a mono-repo, the dependencies on the stubs may not require the artifacts to be published in this way, but this will depend on how your project is set up.

Server-side maintained stubs

  1. The stub source code is written manually and subsequently published to a repository as a JAR file
  2. The client imports the JAR as a dependency and downloads it from the repository
  3. Depending on what is in the Jar, the test loads the stub directly to WireMock or instantiates the dynamic stub (see next section for details) and uses it to set up WireMock stubs and verify the calls

This approach improves the accuracy of the stubs and removes the duplicated effort problem since there is only one set of stubs maintained. There is no issue with visibility either since the stubs are written while having full access to the API definition, which ensures better understanding. The consistency is ensured by the consumers always loading the latest version of the published stubs every time the tests are executed.

However, preparing stubs manually on the producer's side can also have its own shortcomings. It tends to be quite laborious and time-consuming. As any handwritten code intended to be used by 3rd parties, it should be tested, which adds even more effort to the development and maintenance.

Another problem that may occur is a consistency issue. Different developers may write the stubs in different ways, which may mean different ways of using the stubs. This slows development down when developers maintaining different services need to first learn how the stubs have been written, in the worst-case scenario, uniquely for each service.

Also, when writing stubs on the consumer's side, all that is required to prepare are stubs for the specific parts of the API that the consumer actually uses. But providing them on the producer's side means preparing all of them for the entire API as soon as the API is ready, which is great for the client but not so great for the provider.

Overall, writing stubs on the provider side has several advantages over the client-side approach. For example, if the stub-publishing and API-testing are well integrated into the CI pipeline, it can serve as a simpler version of Consumer Driven Contracts, but it is also important to consider the possible implications like the requirement for the producer to keep the stubs in sync with the API.

Dynamic Stubbing

Some developers may define stubs statically in the form of JSON. This is additional maintenance. Alternatively, you can create helper classes that introduce a layer of abstraction — an interface that determines what stubbing is possible. Usually, they are written in one of the higher-level languages like Java/Kotlin.

Such stub helpers enable the clients to set up stubs within the constraints set out by the author. Usually, it means using various values of various types. Hence I call them dynamic stubs for short.

An example of such a dynamic stub could be a function with a signature along the lines of:

Kotlin
 
fun get(url: String, response: String }


One could expect that such a method could be called like this:

Kotlin
 
get(url = "/someResource", response = "{ \"key\" = \"value\" }")


And a potential implementation using the WireMock Java library:

Kotlin
 
fun get(url: String, response: String) {
        stubFor(get(urlPathEqualTo(url))
                .willReturn(aResponse().withBody(response)))
    }


Such dynamic stubs provide a foundation for the solution described below.

Auto-Generating Dynamic WireMock Stubs

I have been working predominantly in the Java/Kotlin Spring environment, which relies on the SpringMVC library to support HTTP endpoints. The newer versions of the library provide the @RestController annotation to mark classes as REST endpoint providers. It's these endpoints that I tend to stub most often using the above-described dynamic approach.

I came to the realization that the dynamic stubs should provide only as much functionality as set out by the definition of the endpoints. For example, if a controller defines a GET endpoint with a query parameter and a resource name, the code enabling you to dynamically stub the endpoint should only allow the client to set the value of the parameter, the HTTP status code, and the body of the response. There is no point in stubbing a POST method on that endpoint if the API doesn't provide it.

With that in mind, I believed there was an opportunity to automate the generation of the dynamic stubs by analyzing the definitions of the endpoints described in the controllers.

Obviously, nothing is ever easy.

A proof of concept showed how little I knew about the build tool that I have been using for years (Gradle), the SpringMVC library, and Java annotation processing.

But nevertheless, in spite of the steep learning curve, I managed to achieve the following:

  • parse the smallest meaningful subset of the relevant annotations (e.g., a single basic resource)
  • design and build a data model of the dynamic stubs
  • generate the source code of the dynamic stubs (in Java)
  • and make Gradle build an artifact containing only the generated code and publish it

(I also tested the published artifact by importing it into another project)

In the end, here is what was achieved:

Multi-repo setup

  1. The annotation processor iterates through all relevant annotations and generates the dynamic stub source code.
  2. Gradle compiles and packages the generated source into a JAR file and publishes it to an artifact repository (e.g., Nexus)
  3. The client imports the JAR as a dependency and downloads it from the repository
  4. The test instantiates the generated stubs and uses them to set up WireMock stubs and verify the calls made to WireMock

Mono-repo setup

With a mono-repo, the situation is slightly simpler since there is no need to package the generated code and upload it to a repository. The compiled stubs become available to the depending subprojects immediately.

These end-to-end scenarios proved that it could work.

The Final Product

I developed a library with a custom annotation @GenerateWireMockStub that can be applied to a class annotated with @RestController. The annotation processor included in the library generates the Java code for dynamic stub creation in tests. The stubs can then be published to a repository or, in the case of a mono-repo, used directly by the project(s).

For example, by adding the following dependencies (Kotlin project):

Groovy
 
kapt 'io.github.lsd-consulting:spring-wiremock-stub-generator:2.0.3'
compileOnly 'io.github.lsd-consulting:spring-wiremock-stub-generator:2.0.3'
compileOnly 'com.github.tomakehurst:wiremock:2.27.2'


and annotating a controller having a basic GET mapping with @GenerateWireMockStub:

Kotlin
 
@GenerateWireMockStub
@RestController
class MyController {

    @GetMapping("/resource")
    fun getData() = MyServerResponse(id = "someId", message = "message")
}


will result in generating a stub class with the following methods:

Java
 
public class MyControllerStub {
    
    public void getData(MyServerResponse response)
        ...
    }

    public void getData(int httpStatus, String errorResponse) {
        ...
    }

    public void verifyGetData() {
        ...
    }

    public void verifyGetData(final int times) {
        ...
    }

    public void verifyGetDataNoInteraction() {
        ...
    }
}


The first two methods set up stubs in WireMock, whereas the other methods verify the calls depending on the expected number of calls — either once or the given number of times, or no interaction at all.

That stub class can be used in a test like this:

Kotlin
 
//Create the stub for the producer’s controller
val myControllerStub = MyControllerStub()

//Stub the controller method with the response
myControllerStub.getData(MyServerResponse("id", "message"))

callConsumerThatTriggersCallToProducer()
myControllerStub.verifyGetData()


The framework now supports most HTTP methods, with a variety of ways to verify interactions.

@GenerateWireMockStub makes maintaining these dynamic stubs effortless. It increases accuracy and consistency, making maintenance easier and enabling your build to easily catch breaking changes to APIs before your code hits production.

More details can be found on the project’s website.

A full example of how the library can be used in a multi-project setup and in a mono-repo:

  • spring-wiremock-stub-generator-example
  • spring-wiremock-stub-generator-monorepo-example

Limitations

The library’s limitations mostly come from the WireMock limitations. More specifically, multi-value and optional request parameters are not quite supported by WireMock. The library uses some workarounds to handle those. For more details, please check out the project’s README.

Note

The client must have access to the API classes used by the controller. Usually, it is achieved by exposing them in separate API modules that are published for consumers to use.

Acknowledgments

I would like to express my sincere gratitude to the reviewers who provided invaluable feedback and suggestions to improve the quality of this article and the library. Their input was critical in ensuring the article’s quality.

A special thank you to Antony Marcano for his feedback and repeated reviews, and direct contributions to this article. This was crucial in ensuring that the article provides clear and concise documentation for the spring-wiremock-stub-generator library.

I would like to extend my heartfelt thanks to Nick McDowall and Nauman Leghari for their time, effort, and expertise in reviewing the article and providing insightful feedback to improve its documentation and readability.

Finally, I would also like to thank Ollie Kennedy for his careful review of the initial pull request and his suggestions for improving the codebase.

API microservice Stub (distributed computing) Spring Framework

Opinions expressed by DZone contributors are their own.

Related

  • Ensuring API Resilience in Spring Microservices Using Retry and Fallback Mechanisms
  • Consumer-Driven Contract Testing With Spring Cloud Contract
  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • Building Mancala Game in Microservices Using Spring Boot (Part 2: Mancala API Implementation)

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!