TestMethodCodeStyle

Plan

  1. Test Class Creation: Create a dedicated test class for the target class or method.
  2. JUnit 5 Annotations: Use JUnit 5 annotations (e.g., @Test, @Nested, @DisplayName) to structure the tests.
  3. Scenario-Based Testing: Write tests for each scenario based on the original method's logic. Ensure that all possible scenarios are covered.
  4. Mocking Dependencies: Use mocking frameworks (e.g., Mockito) to mock dependencies where necessary.
  5. Referencing Classes:
  6. Identify all classes and methods that the target method depends on.
  7. Ensure that these dependencies are either mocked or properly instantiated in the test setup.
  8. If the dependency is complex, provide a simplified implementation or mock behavior to isolate the method under test.
  9. Test Structure:
  10. Create one test method per scenario.
  11. Use a nested class to group all scenarios for a single method.
  12. If the method is tested using assertThrows, include additional assertions to verify the exception's message or properties.
  13. Code Style: Follow the provided code style for test structure.
  14. Parameterization: Use @ParameterizedTest and @CsvSource or @MethodSource for tests with multiple input-output combinations to reduce redundancy.

Example Code

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

public class DemoTest {

    @Nested
    @DisplayName("Tests for demoMethod")
    class DemoMethodTests {

        @Test
        @DisplayName("Scenario 1: When input is valid, then return expected result")
        void whenInputIsValid_thenReturnExpectedResult() {
            // Arrange
            MyService myService = mock(MyService.class);
            String input = "valid-input";
            String expectedOutput = "expected-output";
            when(myService.process(input)).thenReturn(expectedOutput);

            // Act
            String result = myService.process(input);

            // Assert
            assertEquals(expectedOutput, result);
        }

        @Test
        @DisplayName("Scenario 2: When input is invalid, then throw IllegalArgumentException")
        void whenInputIsInvalid_thenThrowIllegalArgumentException() {
            // Arrange
            MyService myService = mock(MyService.class);
            String invalidInput = "invalid-input";
            when(myService.process(invalidInput)).thenThrow(new IllegalArgumentException("Invalid input"));

            // Act & Assert
            IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
                myService.process(invalidInput);
            });
            assertEquals("Invalid input", exception.getMessage());
        }

        @ParameterizedTest
        @CsvSource({
            "input1, output1",
            "input2, output2",
            "input3, output3"
        })
        @DisplayName("Scenario 3: Parameterized test for multiple input-output combinations")
        void parameterizedTest(String input, String expectedOutput) {
            // Arrange
            MyService myService = mock(MyService.class);
            when(myService.process(input)).thenReturn(expectedOutput);

            // Act
            String result = myService.process(input);

            // Assert
            assertEquals(expectedOutput, result);
        }
    }
}