Learnitweb

Unit Testing RESTful Web Services in Spring Boot with MockMvc and Mockito

Testing is a crucial part of developing robust web applications, especially RESTful services built with Spring Boot. This detailed tutorial guides you step by step through the process of unit testing the web, business, and data layers of a Spring Boot application using MockMvcMockito, and key Spring testing features. You’ll also learn best practices for test design, response assertions, and integration testing.

Introduction

Real-world applications typically have a layered architecture:

  • Web Layer: Exposes REST endpoints
  • Business Layer: Contains business logic/services
  • Data Layer: Handles persistence (database operations)

To ensure each part works correctly and efficiently, and to catch bugs early, you should write unit tests for each layer and integration tests for combined functionality.

Why Test in Layers?

  • Isolated testing allows you to test just one part at a time (e.g., only the web/controller layer), making it easier to find and fix bugs.
  • Integration testing ensures multiple layers work together as expected.
  • Mocking is used to simulate or replace real dependencies so tests run quickly and deterministically.

Step 1: Creating a Simple REST Controller

Let’s start by creating a basic REST controller in Spring Boot that returns a simple string.

@RestController
public class HelloWorldController {

    @GetMapping("/hello-world")
    public String helloWorld() {
        return "Hello, world";
    }
}

Step 2: Writing Unit Tests for the Controller

We want to test only the HelloWorldController, not the entire application. For this, Spring’s MockMvc framework is ideal.

@RunWith(SpringRunner.class)
@WebMvcTest(value = HelloWorldController.class)
public class HelloWorldControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void helloWorld_basic() throws Exception {
        mockMvc.perform(get("/hello-world")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello, world"));
    }
}
  • @RunWith(SpringRunner.class) sets up the Spring test context.
  • @WebMvcTest loads only the specified controller (HelloWorldController). It does not load other beans or service layers.
  • mockMvc.perform(...) simulates an HTTP request without starting a server.
  • andExpect allows you to assert the HTTP status, content, headers, etc.

Step 3: Improving Assertions with MockMvc

Beyond basic string assertions, MockMvc provides powerful capabilities for verifying status codes and response content.

Matching the Status

Add assertion for HTTP Status:

.andExpect(status().isOk())

Step 4: Testing Controllers that Return JSON Objects

Let’s return a more complex object from the controller:

@RestController
public class ItemController {

    @GetMapping("/dummy-item")
    public Item getDummyItem() {
        return new Item(1, "Ball", 10, 100);
    }
}

public class Item {
    private int id;
    private String name;
    private int price;
    private int quantity;
    // Getters and setters omitted for brevity
}

Writing a Unit Test for JSON Response

@RunWith(SpringRunner.class)
@WebMvcTest(ItemController.class)
public class ItemControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void dummyItem_basic() throws Exception {
        mockMvc.perform(get("/dummy-item")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().json("{\"id\":1,\"name\":\"Ball\",\"price\":10,\"quantity\":100}"));
    }
}

Why use content().json instead of content().string?

  • content().json is smarter:
    • Ignores field order
    • Ignores whitespace and formatting
    • Allows partial matches (if, for example, you only care about some fields)
  • content().string requires an exact character-for-character match.

For example, if you write:

.andExpect(content().json("{\"id\":1,\"name\":\"Ball\",\"price\":10}"))

The test passes as long as those fields match, even if the actual response contains more fields.

Best Practices and Tips

  • Use @WebMvcTest for controller (web layer) tests.
  • Use MockMvc to issue HTTP requests within tests.
  • Use content().json for JSON assertions—more robust and readable than string matching.
  • Only include additional assertions (assertEquals) if your scenario requires them.
  • Keep tests simple and focused: Test one thing per test method.
  • For complex business logic or database interaction, mock dependencies using Mockito (not covered in this introductory controller-focused tutorial).
  • If you include a business or data layer, write unit tests for those classes separately, and use integration tests to check the complete workflow.
  • Don’t be discouraged if tests feel verbose—the clarity and safety are worth it.