Hands-on Java 25 Series: #3 – Unit Test Mastery with Spring Boot

The Context

A robust testing suite is the cornerstone of any reliable software project and the latest versions of Spring Boot Test make implementing these cases easier than ever. In this articule we focus on Unit Tests, which are designed to ensure that individual components perform as intended in isolation. The goal is early problem detection, which significantly reduces the cost of fixing defects later in the development cycle.


The Problem: how to implement?

The most effective way to structure any test is by following the AAA (Arrange, Act, Assert) pattern. First, you Arrange by setting up the necessary objects and prerequisites. Then, you Act by performing the actual work or calling the function being tested. Finally, you Assert by verifying that the result matches your expected outcome. This consistency makes tests easier to read and maintain.

Important: keep in mind these concepts to review at the Result section:

  • SOLID principles.
  • Single Responsibility Principle.
  • Dependency Inversion.


The Solution

Important Disclaimer: This is a Proof of Concept (PoC) built for a specific course need. If you find this code useful and decide to use it, you do so entirely at your own risk. The authors decline any responsibility for its use.

In our Unit Tests, located in src/test folder, we utilize Mockito in a minimalist way to simulate internal dependencies and external 3rd-party APIs. Mocking is essential for unit tests because it allows us to isolate the logic of the class under test without depending on environment variables, real databases or network conditions. This ensures our tests are repeatable, performant, and do not suffer from side effects.


Before we dive in, the full source code is available on GitHub: 👉 https://github.com/gabo-gil-playground/java-poc-oauth-jqoo-openai


The build.gradle file includes the spring-boot-starter-test, which conveniently bundles JUnit 6, Mockito and AssertJ. This “all-in-one” starter provides everything we need to build a comprehensive suite without hunting for individual libraries:

dependencies {
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

Pro-tip #1: The “magic” of Mockito

An example of a unit test in this project would involve mocking the BlogAIService to test the BlogController logic. By using @Mock and @InjectMocks, we can simulate various responses from the AI service -such as happy paths or error scenarios- without ever making a real network call to OpenAI.


@ExtendWith(MockitoExtension.class)
class BlogServiceTest {
    @Mock
    private BlogAIService aiService;

    @Test
    void testSummarize_Success() {
        // Arrange
        when(aiService.generateSummary(any())).thenReturn("Summary");
        // Act
        String result = service.summarize(content);
        // Assert
        assertEquals("Summary", result);
    }
}

Pro-tip #2: Mock DB behavior

As we saw before, the P.o.C. database retrieval logic resides in BlogSummarizeService.java. As part of unit test implementation, at BlogSummarizeServiceTest.java, we mock all the required DSLContext steps to simulate a type-safe query and the expected result. Using when, any and thenReturn methods from Mockito framework we can test happy and / or unhappy paths behavior without call to a real or test database engine:


/**
 * Scenario:
 * Executes [{@link BlogSummarizeService#getBlogSummarizeList(String)}]
 * Expectation:
 * A {@link List} with summarized texts should be returned
 */
@Test
void whenGetBlogSummarizeListShouldReturnSummarizedTextList() {
	List expectedBlogSummarizeRows = List.of(
		new BlogSummarizeRow(Long.MIN_VALUE, MOCK_BLOG_SUMMARIZE),
		new BlogSummarizeRow(Long.MAX_VALUE, MOCK_BLOG_SUMMARIZE)
	);

	// mocks jooq dsl-context behavior
	SelectSelectStep> selectSelectStep = mock(SelectSelectStep.class);
	SelectJoinStep> selectJoinStep = mock(SelectJoinStep.class);
	SelectConditionStep> selectConditionStep = mock(SelectConditionStep.class);

	when(dslContext.select(any(SelectField.class), any(SelectField.class))).thenReturn(selectSelectStep);
	when(selectSelectStep.from(any(Table.class))).thenReturn(selectJoinStep);
	when(selectJoinStep.where(any(Condition.class))).thenReturn(selectConditionStep);
	when(selectConditionStep.fetchInto(BlogSummarizeRow.class)).thenReturn(expectedBlogSummarizeRows);

	List blogSummarizeList = blogSummarizeService.getBlogSummarizeList(MOCK_USER);

	assertEquals(expectedBlogSummarizeRows.size(), blogSummarizeList.size());
	assertEquals(MOCK_BLOG_SUMMARIZE, blogSummarizeList.getFirst().summarize());
	assertEquals(MOCK_BLOG_SUMMARIZE, blogSummarizeList.getLast().summarize());
}

To run the code, you just need to run gradle test via your terminal or favorite IDE (I personally recommend Intellij). For more details, please, check the README.md file.


Summary of Steps

  1. Define your test dependencies.
  2. In the Arrange phase, instantiate the mock and set return values.
  3. Act by calling the function under test.
  4. Assert that the result matches your expectations.


The Results

Following the SOLID principles is not just about writing better production code: it directly facilitates simpler test cases. For instance, the Single Responsibility Principle ensures that a class does only one thing, meaning its corresponding unit test only needs to verify one behavior. Likewise, Dependency Inversion allows us to easily swap real implementations for mocks during testing.
In conclusion, clear and simple designs lead to clear and simple tests. By applying the AAA pattern and using Mockito efficiently, we can create a suite that provides high confidence with minimal overhead. This disciplined approach to testing ensures that our “development notes” translate into a stable and maintainable production application.

Want to dive deeper into any of these concepts? Drop a comment below and let me know which topic you’d like us to explore in our next Hands-on series!




Do you need help modernizing your legacy applications, optimizing your architecture or improving your development workflows? Let’s connect on LinkedIn or check out my services on Upwork.


Comments

Leave a Reply

Discover more from Gabo Gil

Subscribe now to keep reading and get access to the full archive.

Continue reading