Organizing Test Classes and Test Suites in JUnit 5

Illustration for Organizing Test Classes and Test Suites in JUnit 5
By Last updated:

As applications grow in complexity, so do their test suites. A few isolated unit tests might work for a small project, but enterprise-scale Java applications require structured, organized test classes and suites. Without proper organization, test execution becomes chaotic, debugging failures becomes harder, and CI/CD pipelines slow down.

JUnit 5 introduces a modern way to organize test classes, group related tests, and run test suites efficiently. In this tutorial, we’ll explore different strategies to structure test classes and suites, demonstrate code examples, and share best practices for scaling test efforts.


Why Organize Test Classes and Suites?

  • Maintainability: Easier to understand and maintain large codebases.
  • Separation of Concerns: Keep unit, integration, and E2E tests isolated.
  • Scalability: Allows teams to execute subsets of tests quickly.
  • CI/CD Efficiency: Run relevant tests in pipelines (e.g., unit tests on every commit, integration tests nightly).
  • Collaboration: Provides clarity for multiple developers contributing to a project.

Think of organized test classes like folders in a filing cabinet — without labels and structure, finding documents becomes painful.


Structuring Test Classes in JUnit 5

Naming Conventions

  • Use *Test or *Tests suffixes (e.g., UserServiceTest).
  • Use descriptive method names (shouldSaveUserCorrectly).
  • Separate tests into packages like:
    • unit
    • integration
    • e2e

Example: Simple Test Class

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @Test
    void shouldAddTwoNumbers() {
        Calculator calculator = new Calculator();
        assertEquals(5, calculator.add(2, 3));
    }
}

Nested Tests for Logical Grouping

JUnit 5 supports nested tests using @Nested, which improves readability by grouping related test cases.

import org.junit.jupiter.api.*;

class UserServiceTest {

    @Nested
    class CreateUserTests {
        @Test
        void shouldCreateUser() {
            UserService service = new UserService();
            User user = service.createUser("Alice");
            Assertions.assertNotNull(user);
        }
    }

    @Nested
    class DeleteUserTests {
        @Test
        void shouldDeleteUser() {
            UserService service = new UserService();
            service.deleteUser("Alice");
            Assertions.assertTrue(service.isDeleted("Alice"));
        }
    }
}

Organizing Test Suites in JUnit 5

In JUnit 4, @RunWith(Suite.class) was used. In JUnit 5, we use the JUnit Platform Suite API.

Example: Creating a Test Suite

import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectClasses({
    CalculatorTest.class,
    UserServiceTest.class
})
public class ApplicationTestSuite {
}

This suite runs CalculatorTest and UserServiceTest together.


Selecting Packages Instead of Classes

You can select entire packages to include all tests within them.

import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;

@Suite
@SelectPackages("com.example.myapp.unit")
public class UnitTestSuite {
}

Combining Multiple Suites

JUnit 5 allows combining packages and classes in a single suite.

import org.junit.platform.suite.api.*;

@Suite
@SelectPackages({"com.example.myapp.unit", "com.example.myapp.integration"})
@SelectClasses({CalculatorTest.class, UserServiceTest.class})
public class CombinedSuite {
}

Advanced Features

  • Tags: Use @Tag("integration") to filter tests in CI/CD pipelines.
  • Dynamic Tests: Generate tests programmatically for data-driven scenarios.
  • Parallel Execution: JUnit 5 allows running tests concurrently for faster builds.
  • Extensions: Combine with Spring’s @SpringBootTest for context loading in suites.

Real-World Case Studies

  1. Spring Boot Applications:
    Use @SpringBootTest for integration tests, grouped separately from fast unit tests.

  2. Microservices:
    Organize suites to run contract tests with Pact and containerized integration tests with Testcontainers.

  3. Legacy Codebases:
    Introduce order gradually by grouping existing chaotic tests into suites.


Best Practices

  • Keep test methods independent and isolated.
  • Separate unit, integration, and E2E into different packages.
  • Use tags and suites to control test execution in CI/CD.
  • Avoid overly large suites — group logically.
  • Combine JUnit 5 with Mockito and Testcontainers for real-world scenarios.

Version Tracker

  • JUnit 4 → JUnit 5: Suite API migrated from @RunWith to @Suite and @Select* annotations.
  • Mockito Updates: Inline mocking support makes suites easier to maintain.
  • Testcontainers Growth: Docker Compose integration supports suite-level container orchestration.

Conclusion & Key Takeaways

Organizing test classes and suites in JUnit 5 is about clarity, maintainability, and efficiency. By leveraging nested tests, tagged execution, and suite APIs, developers can structure test suites that scale with project growth and CI/CD demands.

Key Takeaways:

  • Structure tests into packages (unit, integration, e2e).
  • Use @Nested for logical grouping inside test classes.
  • Create reusable suites with @Suite, @SelectClasses, and @SelectPackages.
  • Use tags to control CI/CD execution.

FAQ

1. What replaced @RunWith(Suite.class) in JUnit 5?
The new JUnit Platform Suite API with @Suite and selectors.

2. Can I group tests without creating suites?
Yes, by using @Nested classes and @Tag annotations.

3. How do I run only integration tests?
Use @Tag("integration") and configure Maven/Gradle filters.

4. Can I run multiple suites in parallel?
Yes, JUnit 5 supports parallel test execution.

5. How do I organize tests in a Spring Boot project?
Separate unit tests (src/test/java) and integration tests (src/integrationTest/java).

6. Can I use assumptions in suites?
Yes, assumptions skip tests conditionally even in suites.

7. How do I integrate Mockito with JUnit 5 suites?
Use @ExtendWith(MockitoExtension.class) in test classes within suites.

8. Can Testcontainers be used in suites?
Yes, you can start containers in @BeforeAll and reuse them across tests.

9. Do nested classes work inside suites?
Yes, nested test classes are fully supported in JUnit 5.

10. Should I create one large suite or multiple small ones?
Prefer multiple smaller, logically grouped suites for maintainability.