Microservices Contract Driven Testing

In the world of microservices architecture, ensuring that independently deployable services interact correctly is a significant challenge…

Microservices Contract Driven Testing

In the world of microservices architecture, ensuring that independently deployable services interact correctly is a significant challenge. Contract testing has emerged as an essential approach to tackle this challenge effectively. Spring Cloud Contract, a part of the Spring Cloud suite, provides robust support for contract testing, ensuring that HTTP and message-driven interactions between services are verifiable and consistent. This article delves into the realm of contract testing with Spring Cloud Contract, offering insights into its principles, implementation, and benefits.

The Essence of Consumer Driven Contract testing

CDC testing is fundamentally consumer-centric. It shifts the focus from the service provider to the consumer, ensuring that the services meet the specific needs of their consumers. In this model, the contract is the cornerstone, serving as the definitive source of truth for both parties involved. It outlines how the service provider’s API should respond to requests, thus setting clear expectations.

Advantages of CDC Testing

The adoption of CDC testing brings several benefits to microservices development:

  1. Minimized Integration Issues: By setting clear expectations through contracts, CDC testing reduces the likelihood of integration problems, ensuring that updates or changes in the service provider do not unexpectedly break consumer functionality.
  2. Support for Independent Development: CDC allows services to be developed and deployed independently, as each service only needs to adhere to the contract.
  3. Enhanced Communication: It promotes better communication and understanding between teams, as the contract acts as a focal point for discussions.
  4. Speedier Development Processes: CDC testing can reduce the dependence on extensive end-to-end testing, thereby accelerating the development process.

Implementing CDC in Microservices

Implementing CDC testing typically involves a few key steps:

  1. Contract Creation: The first step is defining the contract. This is usually done in a user-friendly format like JSON or YAML, and tools like Pact, Spring Cloud Contract, or Swagger are often employed for this purpose.
  2. Consumer Side Implementation: On the consumer side, the contract is used to simulate the provider’s responses during testing. This simulation allows the consumer to test its handling of the provider’s data without needing the actual service to be operational.
  3. Provider Side Validation: For the service provider, the contract serves as a basis for testing whether the service meets the consumer’s expectations. This is generally achieved through automated tests that verify the service against the contract.
  4. Contract Management: Effective management of contracts, including sharing and versioning, is crucial. It’s important to have a systematic approach to handle changes in service APIs and ensure ongoing compatibility between different service versions.

Spring Cloud Contract: A Primer

Spring Cloud Contract is a Java-based tool that supports Consumer Driven Contract (CDC) testing. It enables developers to write contracts that define how services should interact, and then generates tests to verify that these contracts are being honored.

Key Concepts of Spring Cloud Contract

  • Contract: A document that describes the expected requests and responses for an API.
  • Producer: The service that exposes an API.
  • Consumer: The service or client that consumes the API.
  • Stubs: Test doubles that mimic the behavior of a service for testing purposes.

Implementing Contract Testing with Spring Cloud Contract

Spring Cloud Contract provides a streamlined approach to implement contract testing. This guide illustrates how to use Spring Cloud Contract to test interactions between simple microservices, with step-by-step examples.

Scenario

Let’s consider two microservices:

  1. User Service (Producer): Provides user details.
  2. Order Service (Consumer): Consumes user details from the User Service.

Step 1: Setting Up the Producer (User Service)

  1. Create a Contract:

In the src/test/resources/contracts directory of the User Service, create a Groovy contract.

user_service_contract.groovy:

Contract.make { 
    request { 
        method 'GET' 
        url '/user/123' 
    } 
    response { 
        status 200 
        body([ 
            id: 123, 
            name: 'John Doe', 
            email: 'john.doe@example.com' 
        ]) 
        headers { 
            contentType(applicationJson()) 
        } 
    } 
}

2. Configure Spring Cloud Contract:

Add the following dependency in your build.gradle or pom.xml:

dependencies { 
    testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier' 
}

3. Generate Tests and Stubs:

Running the build process will generate tests and stubs based on the contract.

Step 2: Implementing the Producer’s API

  1. Create a REST Controller:

In the User Service, implement the REST controller that meets the contract.

UserController.java:

@RestController 
@RequestMapping("/user") 
public class UserController { 
 
    @GetMapping("/{id}") 
    public ResponseEntity<User> getUserById(@PathVariable Long id) { 
        User user = new User(id, "John Doe", "john.doe@example.com"); 
        return ResponseEntity.ok(user); 
    } 
}

👏 Your Support Matters: If you enjoy the content, please give it claps on Medium and share the content away! Your support encourages me to continue creating and sharing valuable Software Engineering resources.


2. Run the Generated Tests:

The generated tests will verify if the User Service adheres to the contract.

Step 3: Setting Up the Consumer (Order Service)

  1. Include Stubs Dependency:

Add the stubs dependency in the Order Service’s build.gradle or pom.xml:

dependencies { 
    testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner' 
}

2. Write Consumer Tests Using the Stubs:

UserServiceClientTest.java:

@RunWith(SpringRunner.class) 
@AutoConfigureStubRunner(ids = {"com.example:user-service:+:stubs:8080"}, 
                         stubsMode = StubRunnerProperties.StubsMode.LOCAL) 
public class UserServiceClientTest { 
 
    @Autowired 
    private UserServiceClient client; 
 
    @Test 
    public void getUserDetails() { 
        User user = client.getUserById(123L); 
        assertEquals("John Doe", user.getName()); 
    } 
}

This test uses the stub generated from the User Service’s contract.

Step 4: Run Consumer Tests

Execute the tests in the Order Service. The tests will interact with the stubs instead of the actual User Service, validating the consumer’s compatibility with the producer.

Best Practices for Consumer Driven Tests in Microservices

1. Clear and Comprehensive Contract Definitions

  • Detail-Oriented Contracts: Ensure that contracts are comprehensive and cover all aspects of the interaction, including request formats, response structures, error handling, and edge cases.
  • Use Understandable Formats: Write contracts in formats that are easily understandable and accessible to both developers and non-technical stakeholders, such as YAML or JSON.

2. Collaborative Contract Development

  • Involve All Stakeholders: Include both the service providers and consumers in the contract creation process. This collaboration ensures that the contracts meet the actual needs and expectations of the consumers.
  • Regular Reviews and Updates: Continuously review and update the contracts to reflect changes in business requirements and service functionalities.

3. Automation of Consumer Driven Tests

  • Integrate with CI/CD Pipeline: Automate the execution of consumer-driven tests and integrate them into the continuous integration and deployment (CI/CD) pipeline to ensure that any changes are immediately tested.
  • Use Mocks and Stubs: Implement mocks and stubs for external services during testing to isolate the service under test and to make the tests reliable and fast.

4. Effective Contract Versioning and Management

  • Version Control for Contracts: Use version control systems to manage changes in contracts, keeping track of all modifications and facilitating rollbacks when necessary.
  • Backward Compatibility Checks: Ensure that new versions of services are backward compatible with existing contracts, avoiding breaking changes that could disrupt the consumers.

5. Consumer-Centric Testing Approach

  • Prioritize Consumer Needs: Design the tests from the consumer’s perspective, focusing on how the consumer will use the service rather than how the service is implemented.
  • Validate Real-world Scenarios: Incorporate real-world usage scenarios in the tests to ensure that the service can handle practical use cases effectively.

6. Handling Multiple Consumers

  • Manage Different Consumer Requirements: Recognize and manage the varying requirements of different consumers, possibly leading to multiple versions of the contract for the same service.
  • Negotiate and Consolidate Contracts: Where possible, negotiate and consolidate requirements among various consumers to minimize the number of different contracts.

7. Observability and Monitoring

  • Monitor Contract Compliance: Implement monitoring tools to continuously observe services’ compliance with their contracts during runtime.
  • Feedback Loops: Establish feedback loops to quickly identify and rectify any issues with service interactions as they arise.

Adhering to these best practices in Consumer Driven Tests is crucial for the success of a microservices architecture. By focusing on clear contracts, collaborative development, effective automation, and consumer-centric approaches, organizations can ensure that their microservices interact seamlessly and meet the evolving needs of their consumers.

🚀 Explore More Resources by Luis Soares

📚 Learning Hub: Dive into the world of Rust programming with my comprehensive collection of resources:

  1. Hands-On Tutorials with GitHub Repos: Get practical experience by following step-by-step tutorials, each accompanied by a dedicated GitHub repository. Access Tutorials
  2. In-Depth Guides & Articles: Understand key Rust concepts through detailed guides and articles, loaded with practical examples. Read More
  3. E-Book: “Mastering Rust Ownership”: Enhance your Rust skills with my free e-Book, a definitive guide to understanding ownership in Rust. Download eBook
  4. Project Showcases: Explore 10 fully functional Rust projects, including an API Gateway, Peer-to-Peer Database, FaaS Platform, Application Container, Event Broker, VPN Server, Network Traffic Analyzer, and more. View Projects
  5. LinkedIn Newsletter: Stay updated with the latest in Rust programming by subscribing to my newsletter on LinkedIn. Subscribe Here

🔗 Connect with Me:

  • Medium: Read my articles on Medium and give claps if you find them helpful. It motivates me to keep writing and sharing Rust content. Follow on Medium
  • Personal Blog: Discover more on my personal blog, a hub for all my Rust-related content. Visit Blog
  • LinkedIn: Join my professional network for more insightful discussions and updates. Connect on LinkedIn
  • Twitter: Follow me on Twitter for quick updates and thoughts on Rust programming. Follow on Twitter

Wanna talk? Leave a comment, and drop me a message!

All the best,

Luis Soares

CTO | Tech Lead | Senior Software Engineer | Cloud Solutions Architect | Rust 🦀 | Golang | Java | ML AI & Statistics | Web3 & Blockchain

Read more