Series "Developing My Own Blog Website" is generated by ChatGPT.

Welcome to part three of my series on developing my own blog website. In the previous posts, I walked through building the blog with Java, Spring Boot, and SQLite, and even containerized the application using Docker. Now that we have a functional blog with CRUD operations, authentication, and database integration, it's time to take the next crucial step: Test-Driven Development (TDD).

In this post, I'll introduce you to the practice of TDD, explain why it's beneficial, and walk through the TDD process in the context of our blog website.


What is Test-Driven Development (TDD)?

Test-Driven Development (TDD) is a development methodology that emphasizes writing tests before writing the actual code. The idea is simple: first, you write a test that describes the behavior you want to implement. Then, you write the code that makes that test pass. Once the test passes, you refactor the code if necessary, keeping it clean and efficient.

TDD follows a simple cycle often referred to as Red, Green, Refactor:

  1. Red: Write a test that fails (because the functionality doesn’t exist yet).
  2. Green: Write the minimal code required to pass the test.
  3. Refactor: Clean up the code while ensuring the test still passes.

Why Use TDD?

There are several compelling reasons to adopt TDD in any project, especially in a growing web application like our blog:

  • Early Bug Detection: Since you're writing tests first, you'll catch issues early before you even write production code.
  • Confidence in Refactoring: With tests in place, you can refactor your code confidently. If anything breaks, your tests will let you know.
  • Better Design: TDD encourages small, focused functions and modules, which leads to cleaner, modular code.
  • Documentation: Your tests serve as a form of documentation, describing the expected behavior of your application.

Setting Up Testing Frameworks in Spring Boot

Spring Boot has built-in support for testing, and the default testing framework is JUnit 5. It also integrates well with Mockito for mocking dependencies, which will allow us to isolate units of code for testing without involving external dependencies like the database.

If you don’t have the necessary dependencies, you can add them to your pom.xml:

<dependencies>
    <!-- JUnit 5 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- Mockito for mocking dependencies -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

With that, you're ready to start writing tests.


TDD in Action: Creating a Blog Post

Let’s say we want to add a feature that allows users to create a blog post. Using TDD, we would first write a test that describes the behavior we expect, and then we'll implement the code to pass the test.

Step 1: Write a Failing Test (Red)

We begin by writing a test for creating a blog post in our BlogPostService.

@SpringBootTest
@ExtendWith(MockitoExtension.class)
public class BlogPostServiceTest {

    @Autowired
    private BlogPostService blogPostService;

    @Test
    public void shouldCreateNewBlogPost() {
        BlogPost blogPost = new BlogPost();
        blogPost.setTitle("My First Blog Post");
        blogPost.setContent("This is my first post on my new blog!");

        BlogPost createdPost = blogPostService.createBlogPost(blogPost);

        assertNotNull(createdPost.getId());
        assertEquals("My First Blog Post", createdPost.getTitle());
    }
}

This test checks that after creating a blog post, the returned object should have a non-null ID and a title matching the input. Since the createBlogPost method doesn’t exist yet, the test will fail at this point—the Red phase.

Step 2: Write Code to Pass the Test (Green)

Now, we write just enough code to make the test pass. Here’s the implementation for BlogPostService:

@Service
public class BlogPostService {

    @Autowired
    private BlogPostRepository blogPostRepository;

    public BlogPost createBlogPost(BlogPost blogPost) {
        return blogPostRepository.save(blogPost);
    }
}

This minimal implementation saves the blog post using BlogPostRepository. After this, the test should pass—the Green phase.

Step 3: Refactor (Clean Up the Code)

After the test passes, we can refactor the code. For example, we might want to add some validation to ensure that the blog post has a title before saving it.

@Service
public class BlogPostService {

    @Autowired
    private BlogPostRepository blogPostRepository;

    public BlogPost createBlogPost(BlogPost blogPost) {
        if (blogPost.getTitle() == null || blogPost.getTitle().isEmpty()) {
            throw new IllegalArgumentException("Title cannot be empty");
        }
        return blogPostRepository.save(blogPost);
    }
}

At this point, our code is more robust, and we can add a test to check the validation:

@Test
public void shouldThrowExceptionWhenTitleIsEmpty() {
    BlogPost blogPost = new BlogPost();
    blogPost.setContent("Content without a title");

    assertThrows(IllegalArgumentException.class, () -> blogPostService.createBlogPost(blogPost));
}

This is how TDD allows you to gradually build up your application by focusing on small, testable chunks of functionality.


Mocking Dependencies with Mockito

In larger applications, services often rely on external dependencies, such as databases or third-party APIs. Testing these services directly can be tricky and slow. That’s where Mockito comes in—it allows us to mock these dependencies.

For instance, if we don’t want to rely on the real database for testing the service, we can mock the BlogPostRepository using Mockito.

Here’s an example of how you can mock the repository in your unit test:

@ExtendWith(MockitoExtension.class)
public class BlogPostServiceTest {

    @InjectMocks
    private BlogPostService blogPostService;

    @Mock
    private BlogPostRepository blogPostRepository;

    @Test
    public void shouldCreateNewBlogPost() {
        BlogPost blogPost = new BlogPost();
        blogPost.setTitle("My First Blog Post");
        blogPost.setContent("This is my first post!");

        when(blogPostRepository.save(any(BlogPost.class))).thenReturn(blogPost);

        BlogPost createdPost = blogPostService.createBlogPost(blogPost);

        assertNotNull(createdPost);
        verify(blogPostRepository).save(blogPost);
    }
}

In this case, we’ve mocked the repository to avoid hitting the actual database during testing. This is a faster and more isolated way to test the service layer.


Applying TDD to Other Features

The beauty of TDD is that you can apply this same process to any feature in your blog. Whether it’s updating a post, deleting a post, or retrieving posts, you follow the same Red, Green, Refactor cycle:

  1. Write a test that describes the functionality you want (e.g., updating a blog post).
  2. Write the minimal code to pass the test.
  3. Refactor the code, keeping it clean and maintainable.

For example, if you want to add a test to retrieve a blog post by ID, you’d follow the same steps:

Test for Retrieving a Blog Post by ID:

@Test
public void shouldReturnBlogPostById() {
    BlogPost blogPost = new BlogPost();
    blogPost.setTitle("Test Blog Post");
    blogPost.setContent("This is a test post.");

    BlogPost savedPost = blogPostService.createBlogPost(blogPost);

    BlogPost retrievedPost = blogPostService.getBlogPostById(savedPost.getId());

    assertEquals(savedPost.getId(), retrievedPost.getId());
}

Implementation to Pass the Test:

public BlogPost getBlogPostById(Long id) {
    return blogPostRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Blog post not found"));
}

Conclusion

By adopting TDD for our blog website, we ensure that every new feature is thoroughly tested before implementation. This process not only helps catch bugs early but also leads to a cleaner and more modular codebase. TDD provides a safety net when refactoring or adding new functionality, making it easier to maintain the code as the project grows.

In this post, I demonstrated how to implement TDD in a Spring Boot application, focusing on the Red, Green, Refactor cycle, using JUnit for testing and Mockito for mocking dependencies. You can now apply this methodology to other parts of your application, ensuring a reliable, maintainable codebase as you continue building new features.