Create your own
Lesson illustration

Provider-Side Contract Verification with Spring Cloud Contract

Hello! Welcome back.

In our last lesson, we took the first crucial step on the provider side of contract testing: we wrote a contract using the Groovy DSL and created the essential BaseContractTest class to prepare our application for testing.

Today, we will complete the picture by implementing and analyzing the full provider-side verification process. Your learning outcome is to implement a provider-side verification test based on a contract generated by Spring Cloud Contract. You will see how Spring Cloud Contract automatically generates and runs tests against your API based on the contract and base class you provide. This mechanism is the core safety net of Consumer-Driven Contract Testing (CDCT).

For your senior-level interviews, being able to articulate not just that you use contract tests, but how the verification process works—what the generated test does, how the base class enables it, and how it prevents breaking changes—is a key differentiator.

1. The Provider-Side Verification Workflow

Let's start by visualizing where today's lesson fits into the overall CDCT process.

Consumer-Driven Contract Testing Workflow
This diagram shows the end-to-end CDCT workflow. Our focus today is squarely on the **Provider** side: defining the contract, running the **generated tests** against our code, and, upon success, producing the stubs that the consumer will later use.

As a quick recap, implementing this verification requires three key pieces in our provider service's codebase:

  1. The Contract: The .groovy file in src/test/resources/contracts that defines the expected API behavior.
  2. The Build Configuration: The spring-cloud-contract-maven-plugin configured in our pom.xml.
  3. The Base Test Class: The abstract Java class that sets up the Spring test context and mocks dependencies.

With these in place, the framework can automatically verify that your API implementation honors the contract.

2. Configuring the Verification Components

While we touched on these in the last lesson, let's solidify their roles in the verification process.

The Maven Plugin Configuration

The spring-cloud-contract-maven-plugin in your pom.xml is the engine that drives the verification.

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>...</version>
    <extensions>true</extensions>
    <configuration>
        <testFramework>JUNIT5</testFramework>
        <baseClassForTests>com.example.producerservice.BaseContractTest</baseClassForTests>
    </configuration>
</plugin>

The <baseClassForTests> entry is critical. It acts as a pointer, telling the plugin, "When you generate a test for a contract, make it extend this specific Java class." This is how the abstract contract gets linked to a concrete test environment.

The Base Test Class: The Bridge to Your Code

The base class is arguably the most important piece of code you'll write for provider-side verification. It creates the bridge between the contract's definition and your application's actual code. Its primary responsibilities are:

  • Loading the Application Context: Using @SpringBootTest to load the necessary beans, typically just your web layer (@WebMvcTest) or a mock environment.
  • Isolating the Controller: Using @MockBean to replace external dependencies (like repositories or other services) with mocks. This ensures you are testing the controller and its HTTP contract in isolation.
  • Mocking Behavior: In a setup method (annotated with @BeforeEach in JUnit 5), you use a mocking framework like Mockito to define how your mocks should behave. This mocked behavior must produce the data that the contract expects.

Testing a Spring Boot REST API against a Contract

Let's examine a clear, well-explained example of a base test class. This article provides a great breakdown of its components and purpose, solidifying the concepts we've just discussed.

Read the section titled 'Test Base'. Notice how @SpringBootTest is used to load the application context, @MockBean isolates the controller from the repository, and the @Before method (or @BeforeEach in JUnit 5) uses Mockito to set up the mock's behavior to match the contract.

3. Triggering and Analyzing the Verification

With the contract written and the base class prepared, you don't need to write the actual JUnit test. You just need to run your build.

mvn clean install

When this command runs, the Spring Cloud Contract plugin automatically performs the following steps during the generate-test-sources phase of the Maven lifecycle:

  1. It scans src/test/resources/contracts for all contract files.
  2. For each .groovy contract, it generates a corresponding JUnit test class inside target/generated-test-sources/contracts/.
  3. These generated tests are then automatically compiled and executed during the test phase.

Let's watch a demonstration of this process. The video shows how the build fails when the API implementation violates the contract and passes once the issue is fixed.

Spring Cloud Contract (HTTP)

This video from the SpringDeveloper channel provides a concise demonstration of running the verification, seeing a failure, and understanding the generated test.

Watch the segment from 30:28 to 33:12. Pay attention to: The successful mvn clean install run. How the presenter intentionally breaks the contract by changing the mock data in the base class. The resulting build failure and the clear error message. The glimpse of the auto-generated ContractVerifierTest.java file.

Dissecting the Generated Test

As you just saw, the generated test is the concrete implementation of the contract. While you don't write this file, understanding its structure is essential for debugging and explaining the process.

Here's an example of what a generated test looks like, adapted from the article "Testing a Spring Boot REST API against a Contract...".

// Located in target/generated-test-sources/contracts/...
// This class is generated automatically!

// 1. Extends the base class you specified
public class YourContractTest extends BaseContractTest {

    @Test
    public void validate_shouldSaveUser() throws Exception {
        // given: Set up the request using RestAssured
        MockMvcRequestSpecification request = given()
            .header("Content-Type", "application/json")
            .body("{\"firstName\":\"Arthur\",\"lastName\":\"Dent\"}");

        // when: Execute the HTTP request against your controller
        ResponseOptions response = given().spec(request)
            .post("/user-service/users");

        // then: Assert the response matches the contract
        assertThat(response.statusCode()).isEqualTo(201);
        assertThat(response.header("Content-Type")).matches("application/json.*");
        
        // and: Use JsonPath to assert the response body structure and values
        DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
        assertThatJson(parsedJson).field("['id']").isEqualTo(42);
    }
}

Key Observations:

  • Inheritance: The test extends your BaseContractTest, inheriting the @SpringBootTest context and mock setup.
  • RestAssured: It uses RestAssured to build and execute the HTTP request defined in the contract's request block.
  • Assertions: It uses a combination of standard assertions and JsonPath to meticulously check that the HTTP status, headers, and body of the actual response match the contract's response block.

If any of these assertions fail, the build breaks, preventing the faulty code from being deployed.

Test your understanding!

You've written a contract and a base class to verify an endpoint GET /api/orders/summary. In your BaseContractTest, you mocked the OrderService to return an OrderSummary object with a field totalAmount.

A colleague renames the field in the OrderSummary DTO from totalAmount to totalValue. They forget to update the contract and the mock in the base class. What happens when they run mvn clean install and why?

Show answer

The mvn clean install build will fail.

Here's the sequence of events:

  1. The Spring Cloud Contract plugin generates a test based on the original contract, which expects a totalAmount field in the JSON response.
  2. The test runs, making a GET request to /api/orders/summary.
  3. Your controller, now using the updated OrderSummary DTO, returns a JSON payload with a totalValue field instead of totalAmount.
  4. The assertion in the auto-generated test, which uses JsonPath to look for $.totalAmount, fails because that field is missing in the actual response. This causes the test to fail, which in turn fails the entire Maven build. This is exactly the safety net we want!

4. The Payoff: Generating Stubs

When the mvn clean install process completes successfully, it does more than just run tests. It produces a critical artifact: the stubs JAR.

  • Example: your-service-0.0.1-SNAPSHOT-stubs.jar

This JAR file is the key deliverable from the provider to its consumers.

Spring Cloud Contract (HTTP)

Let's conclude by understanding what this stubs JAR is and what's inside it. The SpringDeveloper video provides a perfect explanation.

Watch from 33:12 to 34:54. The presenter extracts the contents of the generated stubs JAR and shows that it contains JSON files. Note that these are WireMock mappings, which declaratively define the mock server's behavior.

As you saw, the stubs JAR contains a set of JSON files that are essentially WireMock definitions for all your verified contracts. The consumer team can now take this JAR, run it as a mock server, and test their side of the integration without ever needing to connect to your live service.

Conclusion

In this lesson, we completed the provider-side implementation of consumer-driven contract testing. You now understand the full cycle: from configuration to the automated generation and execution of verification tests.

Key Takeaways:

  • Provider verification is automated: Spring Cloud Contract generates JUnit tests from your Groovy contracts.
  • The base class is your implementation: You must provide an abstract base class that sets up the Spring context and mocks dependencies to match the contract's expectations.
  • The build is the gatekeeper: mvn clean install becomes your safety net. If the API implementation deviates from the contract, the build fails.
  • The output is a stub: A successful verification produces a stubs.jar containing WireMock mappings, which is the artifact shared with consumers.

Next Up

We have successfully locked down the provider's API with a verified contract and produced a stub. Now, it's time to switch hats. In the next lesson, Implement a mock server in consumer-side tests based on a contract, we will act as the consumer team, take the stubs JAR we just created, and use it to test our client code in complete isolation.

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

Sign up