Create your own
Lesson illustration

Contract-Based Mock Server for Consumer Tests

Hello! Welcome to the next lesson in our module on microservices testing.

In our last lesson, we played the role of the API provider. We successfully configured our service to verify itself against a contract and, upon a successful build, generate a stubs.jar file. This file is the tangible output of our contract—a portable, verifiable definition of our API's behavior.

Today, we switch roles and become the API consumer. Your goal is to implement a mock server in consumer-side tests based on a contract to enable isolated development. We will take the stubs.jar generated by the provider and use it to run a mock server, allowing us to test our client-side code without needing the actual provider service to be running.

For your senior-level interviews, explaining how you test consumer services in isolation is critical. It demonstrates your understanding of building decoupled, resilient systems and enabling independent, fast-paced development cycles for different teams—a cornerstone of a successful microservices architecture.

1. The Consumer-Side Workflow: From Stub to Mock Server

Let's revisit our workflow diagram. We are now focusing on the right-hand side, where the consumer takes the stubs and uses them for local testing.

Consumer-Driven Contract Testing Workflow
The workflow shows the consumer downloading the provider's stubs and using them to run a mock server. This allows the consumer's tests to validate their client code against the contract in complete isolation.

The process on the consumer side is straightforward and consists of three main steps:

  1. Add the Dependency: Include the spring-cloud-starter-contract-stub-runner dependency in your consumer service's pom.xml.
  2. Configure the Test: Annotate your test class with @AutoConfigureStubRunner to tell Spring where to find the provider's stubs and how to run the mock server.
  3. Write the Test: Implement a standard integration test that uses your HTTP client to call the mock server and asserts the expected behavior.

2. Setting Up the Consumer Project

First, we need to add the necessary tools to our project. The magic on the consumer side is powered by a single dependency.

Consumer-Driven Contracts with Spring Cloud Contract

This article from rieckpil.de clearly shows the required setup for the consumer side. Pay close attention to the Maven pom.xml.

In the section 'Setting Up the Client-Side for Spring Cloud Contract', review the pom.xml file. Notice the spring-cloud-starter-contract-stub-runner dependency. This is the only dependency you need to add to your test scope to enable this functionality.

This stub-runner dependency pulls in everything needed to download stubs, parse them, and launch a pre-configured WireMock server for your tests.

3. Configuring the Mock Server with @AutoConfigureStubRunner

With the dependency in place, the next step is to configure your test class. This is done with a single, powerful annotation: @AutoConfigureStubRunner.

This annotation instructs Spring Boot to find a specific stub, start a mock server based on it, and make it available for the duration of your test.

Getting Started | Consumer Driven Contracts - Spring

The official Spring Guide provides a concise, code-first example of how to use @AutoConfigureStubRunner. This is a great reference for the core syntax.

Navigate to the section 'Create the contract test' and examine the ContractRestClientApplicationTest.java file. Focus on the @AutoConfigureStubRunner annotation and its parameters.

Let's break down the key attributes of @AutoConfigureStubRunner:

  • ids: This is the most important attribute. It's an array of strings that specifies the Maven coordinates of the stubs you want to use. The format is groupId:artifactId:version:classifier:port.

    • com.example:contract-rest-service:0.0.1-SNAPSHOT:stubs:8100
    • groupId:artifactId: Identifies the provider service.
    • version: Specifies the version of the stub. Crucially, you can use + (e.g., 0.0.1-SNAPSHOT:+) to always pull the latest available version. This is a common practice in CI/CD pipelines to ensure you're always testing against the most recent contract.
    • classifier: Usually stubs, indicating you want the stubs JAR.
    • port: The port on which the mock server will run. Your client code must be configured to call localhost on this port during the test.
  • stubsMode: This tells the stub runner where to find the stubs JAR. The two most common values are:

    • StubRunnerProperties.StubsMode.LOCAL: The default. It looks for the stubs in your local Maven repository (~/.m2/repository). This works perfectly when you run mvn clean install on the provider first.
    • StubRunnerProperties.StubsMode.REMOTE: It downloads the stubs from your configured remote Maven repository (like Nexus or Artifactory). This is used in CI pipelines where different services are built independently.

4. A Practical Demonstration: Seeing It in Action

Now, let's watch a full demonstration of this process. This video will walk you through setting up the consumer test, running it, and seeing how it immediately catches a breaking change.

Spring Cloud Contract (HTTP)

This segment from the SpringDeveloper channel is the best practical demonstration of consumer-side testing. It shows both the 'happy path' and how contract tests save you from integration headaches.

Watch the video from 00:33:49 to 00:39:49. Pay close attention to the following sequence of events: The setup of the @AutoConfigureStubRunner annotation, including the ids and workOffline=true (which is equivalent to stubsMode = LOCAL). The first test run, which fails. Notice the error—it's a mismatch between the field name expected by the consumer (surname) and the field name defined in the contract (name). This is the contract test doing its job perfectly. The presenter fixes the client code to align with the contract. The second test run, which passes instantly. Note the extremely fast execution time—this is the benefit of isolated testing.

This video powerfully illustrates the core value proposition: you find integration bugs in milliseconds during a unit test, not hours or days later during manual QA or, even worse, in production.

Test your understanding!

A provider team has released a new version of their service and published the stubs to your company's Artifactory repository. Your consumer-side contract test, configured with @AutoConfigureStubRunner(ids = "com.example:provider-service:+:stubs:8081", stubsMode = StubRunnerProperties.StubsMode.REMOTE), suddenly starts failing in your CI pipeline. The provider team insists their API change was backward compatible.

What are your first three steps to debug this situation?

Show answer
  1. Analyze the Test Failure: Check the CI logs for the exact failure in the consumer test. It will point to a specific assertion that failed (e.g., an unexpected HTTP status code, a missing JSON field, or a field with the wrong data type). This tells you what the consumer client received that it didn't expect.

  2. Inspect the Stub: Download the new stubs.jar from Artifactory. Unzip it and examine the JSON file inside the mappings directory that corresponds to the failing interaction. This file is the WireMock definition and represents the "source of truth" for the contract. This tells you what the mock server was configured to send.

  3. Compare and Communicate: Compare the behavior defined in the stub (Step 2) with the failure you observed in your test (Step 1). The discrepancy is the root cause. With this concrete evidence, you can go back to the provider team and say, for example, "Your new contract states that the price field is now a string, but our client expects a number. This is a breaking change for us." This data-driven approach is far more effective than arguing about what "backward compatible" means.

5. Beyond Spring: Stubs for Any Client

What if your consumer isn't a Spring application? For example, it could be a React frontend, an Android app, or a service written in Python. The stubs generated by Spring Cloud Contract are still incredibly valuable. You can run the stub runner as a standalone executable JAR, which starts the same WireMock server that any non-JVM client can make HTTP requests to.

Spring Cloud Contract (HTTP)

To round out your knowledge, let's see how you can support non-Spring consumers.

Watch the short clip from 00:39:49 to 00:41:27. The presenter runs a stub-runner-boot.jar from the command line, passing the stub coordinates as arguments. The mock server starts, and they can hit the endpoint with a simple curl command. This is a powerful technique for enabling isolated development for frontend and mobile teams.

Mentioning this capability in an interview shows you think about the entire ecosystem, not just the Java backend.

Conclusion

In this lesson, you've mastered the consumer side of contract testing. By leveraging the provider's stubs, you can create fast, reliable, and isolated tests for your client code. This decouples your team's development and release cycles from the provider's, enabling true microservice agility.

Key Takeaways:

  • The spring-cloud-starter-contract-stub-runner dependency is all you need to get started on the consumer side.
  • The @AutoConfigureStubRunner annotation is the command center, telling your test which stubs to use and how to run the mock server.
  • Using stubs provides an instant feedback loop, catching integration issues early in the development cycle.
  • This approach enables fully isolated development, as the consumer no longer depends on a running instance of the provider service for its tests.
  • The stubs are portable and can be used by non-JVM clients via the standalone stub runner JAR.

Next Up

We've now seen how to implement both provider-side and consumer-side contract tests. But how do these tests fit into the bigger picture alongside other tests, like the integration tests we wrote with Testcontainers? In our next lesson, we will Compare the roles of integration tests and contract tests in a CI/CD pipeline, clarifying when to use each and how they complement each other to build a robust testing strategy.

Can't find a good explanation? Sign up and we'll make it for you

Sign up