Unit Testing in Spring Boot – Part 1: Fundamentals
1. What is Unit Testing?
Definition: Unit testing is the process of testing the smallest units of code (e.g., a method or
class) to ensure they work as expected.
Purpose: To verify that individual pieces of logic are correct and isolated from system-wide
dependencies.
Example: Testing a method like addProduct() in isolation, without involving controllers or
databases.
Code Base Structure for Testing
Main Code Structure Corresponding Test Structure
controller controllerTest
service serviceTest
repository repositoryTest
Each component of the application has a corresponding test file/package.
Code Coverage
Measures how much of your code is covered by unit tests.
Tools like SonarQube analyze test coverage.
High coverage → more reliable and maintainable code.
2. Why Unit Tests Are Important
Key Benefits:
1. Faster feedback: Detect issues early after making changes.
2. Early bug detection: Prevent bugs from reaching production.
3. Ease of maintenance: Lightweight, automatic regression checks.
4. Confidence in changes: Safe refactoring due to automated safety nets.
Scenario Example:
When a developer updates a feature, existing unit tests can detect if functionality elsewhere
breaks — avoiding unexpected production failures.
Bottom line: Your application is incomplete without unit testing.
3. Getting Started: Spring Boot Application Setup
Spring Boot automatically includes a testing dependency:
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
This provides required libraries: JUnit 5, Mockito, Spring Test.
Typical Folder Structure:
main/java/... → Application code
main/resources → Configurations
src/test/java/... → All test classes
src/test/resources → Test configurations
4. Creating a Test Class
1. Tests go under /src/test/java matching the package structure.
2. You can create tests manually or by shortcut (Ctrl+Shift+T/Cmd+Shift+T).
3. Example: Creating ProductServiceTest for ProductService.
5. Writing the First Unit Test
@Test
void myFirstTest() {
[Link]("This is my first unit test");
}
Notes:
Annotate method with @Test from [Link].
When you run it, green means pass, red means fail.
6. Testing a Real Method
Example: Testing [Link]() method.
Steps:
1. Create objects: Instantiate ProductService.
2. Pass Data: Create a Product object to pass.
3. Run Test: Call [Link](product).
This may fail due to NullPointerException since Spring dependencies (like repositories) aren’t
injected automatically.
7. Using Mockito for Mock Objects
Mockito provides the tools to create mock dependencies that replace real objects.
Annotations:
@Mock → Creates mock object.
@InjectMocks → Injects mocks into dependent objects.
@ExtendWith([Link]) → Enables Mockito support in JUnit 5.
Example Setup:
@ExtendWith([Link])
class ProductServiceTest {
@Mock
private ProductRepository productRepository;
@InjectMocks
private ProductService productService;
}
Explanation:
@Mock: Mocks the repository.
@InjectMocks: Injects mocked repository into the service.
No real DB or Spring context is needed.
8. Mocking Behavior in Tests
When method returns something:
when([Link](product)).thenReturn(product);
when(...).thenReturn(...): Simulates behavior of dependency method calls.
Steps Flow:
1. Prepare test data.
2. Mock repository responses.
3. Call actual method.
4. Assert expected result.
9. Writing Assertions
Assertions verify expected outcomes.
Common JUnit Assertions:
Function Purpose
assertEquals(expected, actual) Check equality
assertNotNull(object) Ensure object is not null
assertTrue(condition) Condition should be true
assertFalse(condition) Condition should be false
Example:
assertNotNull(addedProduct);
assertEquals([Link](), [Link]());
Static Imports:
For cleaner test code:
import static [Link].*;
import static [Link].*;
Then you can directly use assertEquals() and when() without class prefixes.
10. Organizing a Test Method
Follow standard test structure for readability:
// Arrange (Data preparation)
Product product = new Product();
// Act (Execute function)
Product addedProduct = [Link](product);
// Assert (Verification)
assertEquals([Link](), [Link]());
Segregate the steps with blank lines — Arrange–Act–Assert pattern (AAA).
11. Error Scenarios and Debugging
NullPointerExceptions indicate missing dependency or object initialization.
Use the debugger to follow the test flow.
If tests fail, investigate the failed assertion message — it tells what was expected vs. actual.
12. Summary and Key Takeaways
Unit tests target smallest logic units (methods/classes).
They increase reliability, reduce regression issues, and promote maintainable code.
Mockito helps to mock dependencies, so tests run isolated.
JUnit 5 provides annotations and assertions for structured test execution.
Always follow good naming conventions for test methods — e.g.,
addProduct_ShouldAddProductSuccessfully.
Keep your test code clean, meaningful, and separated using the AAA pattern.
Next Steps (Covered in Part 2)
In Unit Testing Part 2, we cover:
1. JUnit test life cycle annotations (@BeforeAll, @BeforeEach, @AfterEach, @AfterAll).
2. Testing exception scenarios.
3. Testing private methods using reflections.
4. Handling void methods using Mockito doNothing().
5. Advanced verification with Mockito.
Pro Tip: Treat tests as first-class citizens — well-written tests are the best documentation for
your code and behavior.