Unit Testing Guide
This template provides a comprehensive testing strategy using TestNG groups to categorize tests, allowing you to run different types of tests efficiently.
๐งช Testing Philosophy
Test Pyramid Structure
- Unit Tests (70%): Fast, isolated tests for individual components
- Integration Tests (20%): Tests for module interactions
- End-to-End Tests (10%): Full application workflow tests
TestNG Groups Strategy
Tests are organized using TestNG groups for flexible execution:
- @Test(groups = ["small"]): Fast tests (< 100ms) for pure logic
- @Test(groups = ["medium"]): Tests with lightweight dependencies (< 1s)
- @Test(groups = ["large"]): Integration tests requiring Spring context or external systems
๐๏ธ Test Structure & Naming
Test Method Naming Convention
Use descriptive names with backticks following the pattern: function, condition, expectation
@Test(groups = ["small"])
class ExampleServiceUnitTest {
    
    @Test
    fun `getWelcomeMessage, with custom name, returns personalized message`() {
        // Given
        val name = "Alice"
        
        // When
        val result = exampleService.getWelcomeMessage(name)
        
        // Then
        assertEquals(result, "Hello, Alice! This is your Kotlin Multimodule Template.")
    }
}
TestNG Groups Configuration
Apply groups at the class level for consistent categorization:
/**
 * Small unit tests for ExampleService
 */
@Test(groups = ["small"])
class ExampleServiceUnitTest {
    // All test methods inherit the "small" group
}
๐ Running Tests
By TestNG Groups
# Run only small/fast tests
./gradlew test -PtestGroups=small
# Run medium tests (with mocks/setup)
./gradlew test -PtestGroups=medium
# Run integration tests
./gradlew test -PtestGroups=large
# Run multiple groups
./gradlew test -PtestGroups="small,medium"
# Run all tests (default)
./gradlew test
Additional Test Tasks
# Run integration tests specifically
./gradlew integrationTest
# Run all tests including integration
./gradlew allTests
# Run tests in parallel
./gradlew test -PtestGroups=small --parallel
๐ Coverage Integration
85% Minimum Coverage
The template enforces 85% instruction coverage minimum:
# Run tests with coverage verification
./gradlew test jacocoTestCoverageVerification
# Generate coverage reports
./gradlew jacocoTestReport
# View coverage report
# Opens: build/reports/jacoco/test/html/index.html
Coverage Exclusions
Smart exclusions are automatically applied:
- Configuration classes (**/*Config*,*Application*)
- Data classes (*.dto.*,*.entity.*,*.model.*)
- Kotlin-generated code (**/*$Companion*,**/*$WhenMappings*)
- Exception classes (*.exception.*,**/*Exception*)
๐งช Writing Effective Tests
Small Group Tests (Fast)
Characteristics: Pure logic, no external dependencies, < 100ms
@Test(groups = ["small"])
class ExampleServiceUnitTest {
    @Test
    fun `getWelcomeMessage, with empty string, returns message with empty name`() {
        // Given
        val name = ""
        
        // When
        val result = exampleService.getWelcomeMessage(name)
        
        // Then
        assertEquals(result, "Hello, ! This is your Kotlin Multimodule Template.")
    }
}
Medium Group Tests (With Mocks)
Characteristics: Uses mocks, lightweight dependencies, < 1s
@Test(groups = ["small"])  // Controller tests with mocks are still "small"
class ExampleControllerUnitTest {
    @BeforeMethod
    fun setup() {
        mockExampleService = mockk()
        controller = ExampleController(mockExampleService)
    }
    @Test
    fun `getWelcome, with custom name, returns response with message and timestamp`() {
        // Given
        val name = "Alice"
        every { mockExampleService.getWelcomeMessage(name) } returns "Hello, Alice!"
        // When
        val response = controller.getWelcome(name)
        // Then
        assertEquals(response["message"], "Hello, Alice!")
        verify { mockExampleService.getWelcomeMessage(name) }
    }
}
Large Group Tests (Integration)
Characteristics: Full Spring context, database, external systems
@Test(groups = ["large"])
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ExampleControllerIntegrationTest : AbstractTestNGSpringContextTests() {
    @Test
    fun `getWelcome, end to end, returns valid response`() {
        // Full integration test with real Spring context
    }
}
๐ง Test Configuration
TestNG Groups in Gradle
The template configures TestNG groups via command-line parameters:
// In scripts/testing.gradle
test {
    useTestNG() {
        if (project.hasProperty('testGroups')) {
            String groups = project.property('testGroups')
            includeGroups groups
        }
    }
}
Performance Settings
test {
    // Parallel execution
    maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
    
    // Memory settings
    jvmArgs '-Xmx1g', '-XX:MaxMetaspaceSize=256m'
}
๐ Coverage Quality Gates
Build Integration
# Coverage verification runs automatically with check
./gradlew check  # Includes jacocoTestCoverageVerification
# Quick feedback during development
./gradlew test -PtestGroups=small jacocoTestReport
CI/CD Integration
# GitHub Actions example
- name: Run Small Tests
  run: ./gradlew test -PtestGroups=small
- name: Run All Tests with Coverage
  run: ./gradlew test jacocoTestCoverageVerification
๐ฏ Best Practices
1. Group Classification
- Small: Pure business logic, no I/O, fast execution
- Medium: With mocks, light setup, moderate execution time
- Large: Full context, real dependencies, slower execution
2. Test Naming
// โ
 Good: Descriptive and readable
fun `getWelcomeMessage, with null input, throws IllegalArgumentException`()
// โ Avoid: Unclear purpose
fun testGetWelcomeMessage1()
3. Coverage Strategy
- Focus on business logic: Ensure all service methods are tested
- Test edge cases: Empty strings, null values, boundary conditions
- Mock external dependencies: Keep tests fast and isolated
4. Group Organization
@Test(groups = ["small"])
class ServiceUnitTest {
    // Fast tests for service logic
}
@Test(groups = ["large"])  
class ServiceIntegrationTest {
    // Tests with full Spring context
}
This testing strategy ensures fast feedback during development while maintaining comprehensive coverage and quality standards.