Skip to content

paradigmadigital/mutation-testing-with-gradle

 
 

Repository files navigation

An example of effective mutation testing in Spring Boot 3 projects with Gradle

Ask DeepWiki Java Spring Boot Gradle PostgreSQL License


📋 Table of Contents


📋 Project Overview

This project demonstrates why traditional code coverage tools aren't enough to ensure test quality and how mutation testing can reveal weaknesses in your test suite that JaCoCo and PMD might miss.

Built as an eCommerce cross-selling API using Spring Boot 3, this project showcases the implementation of effective mutation testing strategies using Pitest with the Descartes mutation engine.

This project uses Gradle. If your usual build tool is Maven, check out the analogous project: An example of effective mutation testing in Spring Boot 3 projects with Maven.


🎯 Project Goal

Implement an eCommerce system using Outside-In TDD approach while demonstrating that even with high code coverage (JaCoCo) and static analysis (PMD), your tests might not be as robust as you think.

Mutation testing reveals the truth about test quality.


🏗️ Architecture & Technologies

Core Technologies

  • Java 21
  • Spring Boot 3.5.5
  • Spring Data JPA for data persistence
  • PostgreSQL as the primary database
  • Flyway for database migrations
  • Testcontainers for integration testing

Code Quality & Testing Stack

  • JaCoCo - Traditional code coverage (shows what lines are executed)
  • PMD - Static code analysis (finds potential bugs and code smells)
  • PITest with Descartes - Mutation testing (reveals if your tests actually validate behavior)
  • ArchUnit - to check that the rules for the layers of Hexagonal Architecture are respected
  • Spotless - Code formatting

Additional Tools

  • MapStruct - Bean mapping
  • Lombok - Boilerplate code reduction
  • OpenAPI Generator - API documentation and client generation
  • Swagger/OpenAPI - API documentation

📋 Requirements


🎯 Functional Requirements

1. We have an eCommerce Rest API with the following endpoints:

  • GET /api/products

  • GET /api/products/{productId}

  • GET /api/users/{userId}/basket

  • POST /api/users/{userId}/basket

2. When we enter the products page, the list of all products is returned to us:

  • GET /api/products
[
  {
    "id": 1,
    "name": "Dell Latitude 3301 Intel Core i7-8565U/8GB/512GB SSD/13.3",
    "price": "999,00 €"
  },
  {
    "id": 2,
    "name": "Samsonite Airglow Laptop Sleeve 13.3",
    "price": "41,34 €"
  },
  {
    "id": 3,
    "name": "Logitech Wireless Mouse M185",
    "price": "10,78 €"
  },
  {
    "id": 4,
    "name": "Fellowes Mouse Pad Black",
    "price": "1,34 €"
  }
]

3. When we obtain a product by its ID, the related products are returned next to it:

  • GET /api/products/{productId}
{
  "product": {
    "id": 1,
    "name": "Dell Latitude 3301 Intel Core i7-8565U/8GB/512GB SSD/13.3",
    "price": "999,00 €"
  },
  "cross_selling": [
    {
      "id": 2,
      "name": "Samsonite Airglow Laptop Sleeve 13.3",
      "price": "41,34 €"
    },
    {
      "id": 3,
      "name": "Logitech Wireless Mouse M185",
      "price": "10,78 €"
    }
  ]
}

4. We can get a user's basket by their ID.

  • GET /api/users/{userId}/basket
{
  "id": 1,
  "userId": 1,
  "items": {
    "products": [
      {
        "id": 3,
        "name": "Logitech Wireless Mouse M185",
        "price": "10,78 €"
      }
    ]
  }
}

5. If the customer does not have a basket with products, is returned HttpStatus.NOT_FOUND.

6. We can add products to this basket with POST and the payload:

  • POST /api/users/{userId}/basket
{
  "id": 3,
  "name": "Logitech Wireless Mouse M185",
  "price": "10,78 €"
}

8. When we add a product to a basket and the basket does not exist, it is created automatically and the product is added successfully.

9. When we add a product, it already exists in the basket, it is not added.

10. Use PostgreSQL to save the data.

11. Use native queries to manage cross-sell data.

12. This is the example of the table creations.


⏱️ Non-Functional Requirements

  • Test Coverage: Minimum 80% line coverage
  • Mutation Coverage: Minimum 80% mutation score
  • Static Analysis: Zero PMD violations
  • Code Style: Consistent formatting enforced by Spotless
  • Architecture: Implement a hexagonal architecture with proper layer separation

🚀 Getting Started


Prerequisites

  • Java 21 or higher
  • Docker (for PostgreSQL and Testcontainers)
  • Gradle 8.x (wrapper included)

Build the project

./gradlew clean build

🛠️ Running Tests & Quality Checks

Standard Test Execution

./gradlew test

Generate JaCoCo coverage report

./gradlew jacocoTestReport

View JaCoCo coverage report

open build/reports/jacoco/html/index.html

Check code style and formatting rules

 ./gradlew spotlessCheck

Check PMD rules

./gradlew pmdMain

Mutation Testing

Run the mutation tests

 ./gradlew pitest

View detailed mutation report

open build/reports/pitest/index.html

Docker

Build Docker image:

docker build -t mutation-testing-with-gradle .

Create and run a new container from an image:

docker run -d -p 8080:8080 --name mutation-testing-with-gradle mutation-testing-with-gradle

Run with docker compose

docker compose up -d

Access

URL to access to Swagger UI

Stop docker containers:

docker compose down -v

🎯 Key Learning Outcomes

By exploring this project, you'll understand:

  1. Why 100% code coverage can be misleading
  2. How mutation testing reveals test weaknesses that traditional metrics miss
  3. The difference between testing code execution vs. testing behavior
  4. Practical strategies for writing more effective tests

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Write tests first (TDD approach)
  4. Ensure all quality gates pass (./gradlew check)
  5. Commit your changes (git commit -m 'Add some amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

Quality Standards

  • Minimum 80% line coverage (JaCoCo)
  • Minimum 80% mutation coverage (PITest)
  • All PMD rules must pass
  • All tests must be green

📚 Further Reading


📝 License

This project is licensed under the MIT License - see the LICENSE file for details.


If this project helped you understand mutation testing better, please give it a star!

Code coverage tells you what code your tests execute. Mutation testing tells you if your tests actually validate anything meaningful.

About

An example of effective mutation testing in Spring Boot 3 projects with Gradle

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 99.7%
  • Dockerfile 0.3%