Writing automated tests is one of the best investments you can make in your codebase. It helps catch bugs early, documents your intended behavior, and lets you refactor safely. In this guide, we’ll walk through the very basics and build up to using JUnit 5, Mockito, and MockMvc to test controllers and services in a Spring Boot application—complete with all the key annotations, patterns, and examples a beginner needs.
📚 Table of Contents
- Why Test?
- JUnit 5 Basics
- Mockito Basics
- Testing a Simple Controller with MockMvc
- Testing a Service Class with Mockito
- Annotation Cheat‑Sheet (Copyable Block)
- Putting It All Together: Mini‑Project Walkthrough
- Best Practices & Tips
1. Why Test?
- Early bug detection: Tests run in milliseconds, so you find mistakes before they reach production.
- Safety net for refactoring: Change code with confidence—if a test breaks, you know exactly what’s broken.
- Living documentation: Tests show how your code is supposed to work more clearly than comments.
💡 Even a small app benefits from a handful of tests on its most important parts!
2. JUnit 5 Basics
JUnit is the standard Java testing framework. JUnit 5 (“Jupiter”) gives you:
-
Annotations
-
@Test: mark a method as a test case -
@BeforeEach/@AfterEach: run setup/cleanup before and after each test -
@BeforeAll/@AfterAll: run once before/after all tests (static methods) -
@DisplayName: give a human‑readable name to a test
-
-
Assertions
- Basic:
assertEquals(expected, actual),assertTrue(condition),assertThrows(...) - Fluent (via AssertJ):
assertThat(actual).isEqualTo(expected)
- Basic:
Lifecycle: JUnit discovers and runs your tests, applies setup/teardown annotations, and reports results.
Example: Testing a Plain Java Method
import org.junit.jupiter.api.*;
class CalculatorTest {
private Calculator calc;
@BeforeAll
static void initAll() { System.out.println("Starting Calculator Tests"); }
@BeforeEach
void init() { calc = new Calculator(); }
@Test
@DisplayName("2 + 3 = 5")
void addsTwoNumbers() {
Assertions.assertEquals(5, calc.add(2, 3), "2 + 3 should equal 5");
}
@Test
void throwsOnDivideByZero() {
Assertions.assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
}
@AfterEach
void tearDown() { /* cleanup if needed */ }
@AfterAll
static void tearDownAll() { System.out.println("All tests done"); }
}
3. Mockito Basics
When your code depends on external collaborators—such as databases, web clients, or other services—you want to isolate the class under test. Mocking fakes out those collaborators so you can:
- Control behavior: decide what a method should return (or throw).
- Verify interactions: assert that a collaborator was called with specific arguments.
- Avoid side effects: no real database, no network calls.
Key Annotations
-
@ExtendWith(MockitoExtension.class)— enable Mockito support in JUnit 5 -
@Mock— create a mock instance of an interface or class -
@InjectMocks— instantiate the class under test and inject its@Mockfields -
@Spy— partial mock: real methods run unless stubbed -
@Captor— capture method arguments for later assertions -
@MockBean— (Spring Boot only) replace a bean in the application context with a mock
Example: Mocking a Repository in a Service Test
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
UserRepository repo; // fake repository
@InjectMocks
UserService service; // real service with fake repo injected
@Test
void getUserName_userExists_returnsName() {
when(repo.findById(1L))
.thenReturn(Optional.of(new User(1L, "Alice")));
String name = service.getUserName(1L);
assertEquals("Alice", name);
verify(repo).findById(1L); // ensure method was called
}
@Test
void getUserName_userNotFound_returnsUnknown() {
when(repo.findById(2L)).thenReturn(Optional.empty());
assertEquals("Unknown", service.getUserName(2L));
}
}
4. Testing a Simple Controller with MockMvc
Spring’s MockMvc lets you test @RestController methods without starting a real server. You simulate HTTP requests and inspect responses.
4.1 Controller Code
@RestController
@RequestMapping("/test")
public class SampleController {
@GetMapping
public String sampleMethod() {
return "Application Deployed on AWS EC2";
}
}
4.2 Test Code (JUnit + MockMvc)
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(SampleController.class)
class SampleControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void sampleMethod_returnsExpectedString() throws Exception {
mockMvc.perform(get("/test"))
.andExpect(status().isOk())
.andExpect(content().string("Application Deployed on AWS EC2"));
}
}
Common Pitfall:
Don’t annotate@WebMvcTestwith your test class—you must specify the controller class you’re testing.
5. Testing a Service Class with Mockito
Service classes contain business logic. If they depend on repositories or other services, you mock those collaborators.
5.1 Service Code
@Service
public class SampleService {
private final SampleRepository repo;
public SampleService(SampleRepository repo) {
this.repo = repo;
}
public String getData() {
return repo.fetchData();
}
}
5.2 Test Code
import org.junit.jupiter.api.Test;
import org.mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class SampleServiceTest {
@Mock
private SampleRepository repo;
@InjectMocks
private SampleService service;
@Test
void getData_returnsRepoData() {
when(repo.fetchData()).thenReturn("Data from DB");
String data = service.getData();
assertEquals("Data from DB", data);
verify(repo).fetchData();
}
@Test
void getData_repoThrows_exceptionPropagates() {
when(repo.fetchData()).thenThrow(new RuntimeException("DB down"));
assertThrows(RuntimeException.class, service::getData);
}
}
6. Annotation Cheat‑Sheet
// JUnit 5 Annotations
@Test // Marks a method as a test case
@BeforeEach // Runs before every test for setup
@AfterEach // Runs after every test for cleanup
@BeforeAll // Runs once before all tests (static method)
@AfterAll // Runs once after all tests (static method)
@DisplayName // Gives a custom name to a test method
// Mockito Annotations
@ExtendWith(MockitoExtension.class) // Enables Mockito in JUnit 5 tests
@Mock // Creates a mock instance
@InjectMocks // Injects mocks into the class under test
@Spy // Partial mock: real methods run unless stubbed
@Captor // Captures arguments passed to mocks
@MockBean // Replaces a Spring bean in the test context
// Spring Test Annotations
@WebMvcTest(Controller.class) // Slice test for controllers
@SpringBootTest // Full integration test with application context
@DataJpaTest // Slice test for JPA repositories
@AutoConfigureMockMvc // Auto-configures MockMvc in full-context tests
7. Putting It All Together: Mini‑Project Walkthrough
- Create a Spring Boot project with packages:
-
controller→SampleController -
service→SampleService -
repository→SampleRepository
Write Controller returning a simple string.
Write Service that calls repository.
Write Tests in
src/test/java:
-
Controller Test with
@WebMvcTest(no real server) - Service Test with Mockito
- Run Tests:
- IDE (right‑click → Run)
- CLI:
./mvnw test/mvn test
By the end, you’ll have isolated, fast, and clear tests for each layer of your application.
8. Best Practices & Tips
- Arrange–Act–Assert: structure every test for readability.
-
Name tests descriptively:
methodName_condition_expectedResult. - Test both happy & sad paths (normal and error cases).
- Keep tests fast: mock external collaborators; avoid real I/O.
- Avoid over‑mocking: only mock what you need.
-
Leverage slice tests:
@WebMvcTestfor controllers,@DataJpaTestfor repositories.
With JUnit, Mockito, and MockMvc in your toolbox, you’re ready to build rock‑solid Spring Boot applications. Happy testing!

Top comments (0)