6. πŸ§ͺ Testing Fundamentals πŸ”₯πŸ”₯ (2 hrs)

Ensure the reliability of your Todo application through comprehensive testing.


πŸ“ Project Structure (Recap)

src
β”œβ”€β”€ main
β”‚   β”œβ”€β”€ java/com/example/todo
β”‚   β”‚   β”œβ”€β”€ controller/TodoController.java
β”‚   β”‚   β”œβ”€β”€ model/Todo.java
β”‚   β”‚   β”œβ”€β”€ repository/TodoRepository.java
β”‚   β”‚   β”œβ”€β”€ service/TodoService.java
β”‚   β”‚   β”œβ”€β”€ service/impl/TodoServiceImpl.java
β”‚   β”‚   └── TodoApplication.java
β”‚   └── resources
β”‚       └── application.yml
└── test
    └── java/com/example/todo
        β”œβ”€β”€ TodoServiceTest.java
        β”œβ”€β”€ TodoControllerTest.java
        β”œβ”€β”€ TodoRepositoryTest.java
        └── TodoContainerTest.java

πŸš€ What You’ll Build


🧠 Key Concepts


πŸ§ͺ Unit Test: TodoService

@ExtendWith(MockitoExtension.class) // Enables Mockito support in JUnit 5
class TodoServiceTest {

    @Mock // Creates a mock of the repository so we don’t touch the DB
    private TodoRepository todoRepository;

    @InjectMocks // Injects the above mock into TodoServiceImpl
    private TodoServiceImpl todoService;

    @Test
    void createTodo_ShouldReturnSavedTodo() {
        // Arrange: Create a sample Todo
        Todo todo = new Todo("Test Todo", "Description", false);

        // Mock behavior: when save() is called, return the same todo
        when(todoRepository.save(any(Todo.class))).thenReturn(todo);

        // Act: Call the method under test
        Todo result = todoService.create(todo);

        // Assert: Check the returned Todo object
        assertNotNull(result); // Should not be null
        assertEquals("Test Todo", result.getTitle()); // Title should match

        // Verify: Ensure save() was called on the repository
        verify(todoRepository).save(any(Todo.class));
    }
}

Explanation:


🌐 API Test: TodoController

@WebMvcTest(TodoController.class) // Tests only the controller layer
class TodoControllerTest {

    @Autowired
    private MockMvc mockMvc; // Simulates HTTP requests

    @MockBean
    private TodoService todoService; // Mocks the service to isolate controller

    @Test
    void getAllTodos_ShouldReturnTodoList() throws Exception {
        // Arrange: Mock service response
        List<Todo> todos = Arrays.asList(
            new Todo("Todo 1", "", false),
            new Todo("Todo 2", "", true)
        );
        when(todoService.findAll()).thenReturn(todos);

        // Act & Assert: Perform request and verify response
        mockMvc.perform(get("/api/todos"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$", hasSize(2)))
               .andExpect(jsonPath("$[0].title", is("Todo 1")));
    }
}

πŸ§ͺ Repository Integration Test

@DataJpaTest // Loads only JPA components, fast and isolated
class TodoRepositoryTest {

    @Autowired
    private TodoRepository todoRepository;

    @Test
    void testSaveAndFind() {
        // Save a Todo
        Todo todo = new Todo("Write tests", "Important", false);
        Todo saved = todoRepository.save(todo);

        // Fetch it back
        Optional<Todo> found = todoRepository.findById(saved.getId());

        // Verify it exists and has correct title
        assertTrue(found.isPresent());
        assertEquals("Write tests", found.get().getTitle());
    }
}

πŸ§ͺ Test with PostgreSQL Using Testcontainers

Add this dependency to pom.xml:

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>postgresql</artifactId>
  <version>1.19.0</version>
  <scope>test</scope>
</dependency>

Example test:

@Testcontainers // Enables Testcontainers lifecycle
@DataJpaTest
class TodoContainerTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .withDatabaseName("todo_db")
        .withUsername("todo_user")
        .withPassword("secret");

    @DynamicPropertySource // Overrides Spring Boot DB properties
    static void overrideProps(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 TodoRepository todoRepository;

    @Test
    void containerDbTest() {
        todoRepository.save(new Todo("Container Todo", "", false));

        List<Todo> todos = todoRepository.findAll();
        assertFalse(todos.isEmpty());
    }
}

πŸ“Š Code Coverage with Jacoco

Add to pom.xml:

<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.8</version>
  <executions>
    <execution>
      <goals><goal>prepare-agent</goal></goals>
    </execution>
    <execution>
      <id>report</id>
      <phase>verify</phase>
      <goals><goal>report</goal></goals>
    </execution>
  </executions>
</plugin>

Then run:

mvn test
mvn verify
open target/site/jacoco/index.html

βœ… Summary

Next: Deployment & Docker ➑️