Post

Spring Boot JUnit Test Best Practices - Speed, Reliability, and Maintainability

Learn Spring Boot JUnit best practices to write fast, reliable, and maintainable tests with tags, slices, and context optimization.

Spring Boot JUnit Test Best Practices - Speed, Reliability, and Maintainability

Testing is an essential part of building Spring Boot applications. But as your project grows, running all tests on every code change can become slow, frustrating, and error-prone.

Using JUnit 5 features, Spring Boot test slices, and modern best practices, you can write tests that are fast, reliable, and maintainable—while still providing complete coverage of your application.

Organize Tests by Type

A Spring Boot project typically contains multiple layers of tests. Separating them helps improve speed and clarity.

Test Type Purpose Tools / Annotations
Unit Test Java logic without Spring JUnit 5, Mockito
Slice Test specific Spring layer @WebMvcTest, @DataJpaTest
Integration Test full Spring context @SpringBootTest
Container Test with real services via Testcontainers @SpringBootTest + @Testcontainers
E2E Test full workflows Selenium, RestAssured, Testcontainers

Load the Spring context only when necessary to reduce test runtime.

Use JUnit Tags to Control Test Execution

JUnit 5 @Tag annotations allow you to label tests by type, performance, or feature.

1
2
3
4
5
6
7
8
9
10
@Tag("unit")
class PriceCalculatorTest {}

@Tag("web")
@WebMvcTest(ProductController.class)
class ProductControllerTest {}

@Tag("integration")
@SpringBootTest
class OrderIntegrationTest {}

Benefits:

  • Run only the tests you need
  • Faster local builds
  • Better CI/CD pipeline control

Prefer Slice Tests Over Full Context

Slice tests load only the necessary Spring beans instead of the full application context.

Slow

1
2
@SpringBootTest
class UserControllerTest {}

Fast

1
2
@WebMvcTest(UserController.class)
class UserControllerTest {}

Slice tests are ideal for controllers, repositories, and JSON serialization.

Constructor Injection in Tests

Avoid field injection; constructor injection makes dependencies explicit and testable.

1
2
3
4
5
private final UserService service;

UserServiceTest(UserService service) {
    this.service = service;
}

Benefits:

  • Clear dependencies
  • Immutable setup
  • Compatible with JUnit 5

Use Testcontainers Efficiently

Testcontainers allow you to test real infrastructure (PostgreSQL, Kafka, Redis) but can be slow.

Best Practices

  • Use static containers to start only once per suite
1
2
3
4
5
@Testcontainers
class DatabaseTest {
    @Container
    static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16");
}
  • Reuse containers across tests
  • Avoid using Testcontainers for simple unit or slice tests

Mock External Systems

Tests should not connect to live:

  • Blob storage
  • Payment gateways
  • Email services
  • Third-party APIs
  • Message brokers

Use Mockito, WireMock, or @MockBean for external dependencies.

Avoid Real Databases in Unit Tests

  • Use H2 or in-memory DBs
  • Use Testcontainers only for integration tests

Prioritize Fast Test Suites

Target durations:

  • Unit tests: < 50 ms
  • Slice tests: 200–500 ms
  • Integration tests: 1–3 s
  • Container tests: 5–15 s
This post is licensed under CC BY 4.0 by the author.