Learnitweb

Writing Unit Tests for Spring Boot Controllers with a Mocked Business Service

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 of ItemBusinessService 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:

  1. Missing @Component on Business Service
    Make sure ItemBusinessService is annotated with @Component (in the main app, not the test).
  2. Wrong JSON Format in Test
    Ensure the expected JSON in andExpect(content().json(...)) is a valid JSON string. Every opening brace { must have a corresponding closing brace }.
  3. Mock Returning Null
    If you forget to mock the return value (when(...).thenReturn(...)), the mock will return null by default, and your test will likely fail.