Skip to content

Testing Standards

25.1. JUnit 5 Test Writing Criteria

25.1.1. Test Class Naming

TargetNaming ConventionExample
Unit Test{TargetClass}TestUserServiceTest
Integration Test{TargetClass}IntegrationTestUserControllerIntegrationTest
Slice Test{TargetClass}Test + distinguished by annotationUserControllerTest (@WebMvcTest)

25.1.2. Test Method Naming

Descriptive method names in the project's primary language are permitted. The test intent must be clearly expressed.

java
@Test
void findsUserByEmail() {
  // ...
}

@Test
void throwsExceptionWhenUserNotFound() {
  // ...
}

25.1.3. Given-When-Then Pattern

All tests must be written using the Given-When-Then structure.

java
@Test
void findsUserByEmail() {
  // given
  String email = "user@tienipia.com";
  User expected = new User(1L, "John Doe", email);
  given(userRepository.findByEmail(email)).willReturn(Optional.of(expected));

  // when
  User result = userService.findByEmail(email);

  // then
  assertThat(result.getName()).isEqualTo("John Doe");
  assertThat(result.getEmail()).isEqualTo(email);
}

25.1.4. Assertion Library

  • AssertJ must be used as the standard.
  • JUnit 5's built-in Assertions may also be used, but AssertJ's fluent API is recommended.
java
// AssertJ (recommended)
assertThat(result).isNotNull();
assertThat(result.getName()).isEqualTo("John Doe");
assertThat(users).hasSize(3).extracting("name").contains("John Doe");

// Exception verification
assertThatThrownBy(() -> userService.findById(999L))
    .isInstanceOf(UserNotFoundException.class)
    .hasMessageContaining("999");

25.2. Test Classification

25.2.1. Unit Tests

  • A single class or method must be tested in isolation.
  • External dependencies must be mocked using Mockito.
  • The Spring context must not be loaded.
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

  @Mock
  private UserRepository userRepository;

  @InjectMocks
  private UserServiceImpl userService;

  @Test
  void createsUser() {
    // given
    CreateUserRequest request = new CreateUserRequest("John Doe", "user@tienipia.com");
    given(userRepository.insert(request.name(), request.email()))
        .willReturn(UsersRecordFixture.create(1L, "John Doe", "user@tienipia.com"));

    // when
    UserResponse response = userService.createUser(request);

    // then
    assertThat(response.name()).isEqualTo("John Doe");
    then(userRepository).should().insert(request.name(), request.email());
  }
}

25.2.2. Slice Tests

Slice tests load only specific layers for testing.

AnnotationTarget LayerPurpose
@WebMvcTestControllerAPI request/response verification
@JooqTestRepositoryjOOQ query verification
@JsonTestJSON SerializationDTO conversion verification
java
@WebMvcTest(UserController.class)
class UserControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private UserService userService;

  @Test
  void retrievesUserList() throws Exception {
    given(userService.findAll())
        .willReturn(List.of(new UserResponse(1L, "John Doe", "user@tienipia.com")));

    mockMvc.perform(get("/api/users"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].name").value("John Doe"));
  }
}

25.2.3. Repository Tests

jOOQ-based Repository tests must use @JooqTest or @SpringBootTest + Testcontainers.

@JooqTest Usage Example:

java
@JooqTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class UserRepositoryTest {

  @Container
  static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
      .withDatabaseName("flowin_test")
      .withUsername("test")
      .withPassword("test");

  @DynamicPropertySource
  static void configureProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.username", postgres::getUsername);
    registry.add("spring.datasource.password", postgres::getPassword);
  }

  @Autowired
  private DSLContext dsl;

  private UserRepository userRepository;

  @BeforeEach
  void setUp() {
    userRepository = new UserRepository(dsl);
  }

  @Test
  void createsAndRetrievesUser() {
    // given
    userRepository.insert("John Doe", "user@tienipia.com");

    // when
    Optional<UsersRecord> result = userRepository.findByEmail("user@tienipia.com");

    // then
    assertThat(result).isPresent();
    assertThat(result.get().getName()).isEqualTo("John Doe");
  }
}
  • Testcontainers runs an actual PostgreSQL container, enabling accurate testing of PostgreSQL-specific features (indexes, types, etc.).
  • Flyway migrations are automatically applied to initialize the test database.

25.2.4. Integration Tests

  • @SpringBootTest must be used to load the full context.
  • Testcontainers PostgreSQL must be used instead of an actual database. H2 must not be used.
  • Integration tests must be executed in CI.

25.3. Coverage Criteria

25.3.1. Minimum Coverage

MetricMinimum Threshold
Line Coverage80% or higher
Branch Coverage70% or higher

25.3.2. Exclusions

The following items must be excluded from coverage measurement.

  • Configuration classes (*Config.java)
  • DTOs (simple data classes)
  • Main classes (*Application.java)
  • jOOQ auto-generated code (jooq/ package)

JaCoCo exclusion configuration:

xml
<configuration>
  <excludes>
    <exclude>**/*Config.*</exclude>
    <exclude>**/*Application.*</exclude>
    <exclude>**/dto/**</exclude>
    <exclude>**/jooq/**</exclude>
  </excludes>
</configuration>

25.3.3. Coverage Reports

  • JaCoCo generates HTML reports in target/site/jacoco/.
  • Reports must be stored as artifacts in CI for reference during reviews.

25.4. Test Data Management

25.4.1. Test Fixtures

  • Test data creation must use factory methods or the builder pattern.
  • Sharing data between tests is prohibited. Each test must execute independently.
java
class UserFixture {

  public static User createDefaultUser() {
    return new User(1L, "John Doe", "user@tienipia.com");
  }

  public static User createUser(String name, String email) {
    return new User(null, name, email);
  }
}

25.4.2. Test Principles

  • Tests must be independent. They must not depend on execution order.
  • Tests must be repeatable. They must not depend on external state.
  • Tests must execute quickly. The goal is for all unit tests to complete within 30 seconds.

TIENIPIA QUALIFIED STANDARD