Overview
In this tutorial, we will take the next step in our Spring Boot testing journey. Previously, we tested a simple controller method that returned a hardcoded JSON response. Now, we’ll introduce a business service into the controller, and learn how to write unit tests that mock the service, allowing us to test the controller in isolation.
This is a foundational concept in unit testing—testing a single component without depending on other layers like services or databases.
Step 1: Refactor Controller to Use a Business Service
Previously, your controller might have returned hardcoded data directly. In a real-world scenario, business logic is not implemented inside the controller. Instead, it’s delegated to service classes.
Example: Controller with Direct Hardcoding (Before)
@GetMapping("/dummy-item") public Item dummyItem() { return new Item(1, "Ball", 10, 100); }
Refactor to Use a Business Service (After)
We’ll now delegate to a ItemBusinessService
.
@Autowired private ItemBusinessService businessService; @GetMapping("/item-from-business-service") public Item itemFromBusinessService() { return businessService.retrieveHardcodedItem(); }
Create the Business Service Class
@Component public class ItemBusinessService { public Item retrieveHardcodedItem() { return new Item(1, "Ball", 10, 100); } }
We are hardcoding the response here for simplicity. Later, this method can be extended to talk to a database.
Step 2: Writing the Unit Test for the Controller
We now focus on writing a unit test for the controller, but with a twist: we mock the business service.
Why Mock the Service?
We don’t want the unit test for the controller to depend on the logic in the business service. Our goal is to test only the controller, and mocking helps achieve this separation.
@WebMvcTest(ItemController.class) public class ItemControllerTest { @Autowired private MockMvc mockMvc; @MockBean private ItemBusinessService businessService; @Test public void itemFromBusinessService_basic() throws Exception { // Arrange when(businessService.retrieveHardcodedItem()) .thenReturn(new Item(2, "Item2", 10, 10)); // Act & Assert mockMvc.perform(get("/item-from-business-service")) .andExpect(status().isOk()) .andExpect(content().json(""" { "id": 2, "name": "Item2", "price": 10, "quantity": 10 } """)); } }
Key Points:
@WebMvcTest(ItemController.class)
tells Spring to load only the controller-related components.@MockBean
creates a mock ofItemBusinessService
and injects it into the controller.when(...).thenReturn(...)
sets up the mocked behavior.
Step 3: Common Pitfalls and Debugging
If your test fails, here are some common issues and how to fix them:
- Missing @Component on Business Service
Make sureItemBusinessService
is annotated with@Component
(in the main app, not the test). - Wrong JSON Format in Test
Ensure the expected JSON inandExpect(content().json(...))
is a valid JSON string. Every opening brace{
must have a corresponding closing brace}
. - Mock Returning Null
If you forget to mock the return value (when(...).thenReturn(...)
), the mock will returnnull
by default, and your test will likely fail.