How to Test Spring Boot Web Controller the Right Way

Last Updated:  March 13, 2025 | Published: March 16, 2025

Testing controllers in Spring Boot can feel like a balancing act.

You want fast, focused tests that catch real-world issues—without slowing down your suite or missing critical behavior. Too often, developers fall into one of two traps: writing plain unit tests with Mockito and JUnit, or reaching for the full-blown @SpringBootTest.

Both miss the mark. In this post, I’ll show you why – and how @WebMvcTest strikes the perfect balance, preserving HTTP semantics while keeping your tests lean and powerful.

The Pitfalls of Plain Unit Tests

Let’s start with the common go-to: a plain unit test for your controller using JUnit and Mockito. It’s tempting – mock out the service layer, call your controller method directly, and assert the result. Quick, isolated, done. Right?

Not quite. When you test a controller this way, you lose something critical: HTTP semantics. Controllers in Spring Boot aren’t just random classes—they’re the entry point to your web application, handling requests, responses, status codes, headers, and security.

Bypassing the HTTP layer with a plain unit test means you’re not testing how your controller behaves in the real world.

Here’s what you miss:

  • Request Mapping: Does /api/users/{id} actually resolve to your method?
  • Status Codes: Will a bad request return a 400 or an accidental 200?
  • Headers: Are you setting Content-Type or custom headers correctly?
  • Security: Is your @PreAuthorize rule enforced?

Mocking everything with Mockito might verify your method logic, but it skips the glue that makes a controller a controller. You’re testing a hollow shell, not the real thing.

Why @SpringBootTest Is Overkill

On the flip side, some developers swing too far the other way: @SpringBootTest. This annotation spins up your entire Spring Boot application – database, services, beans, everything.

Sure, it tests your controller in a “real” environment, complete with HTTP semantics via a TestRestTemplate or WebTestClient. But it’s like using a sledgehammer to crack a walnut.

Here’s the problem:

  • Slow Startup: Loading the full application context takes 20–45 seconds (or more), slowing your test suite.
  • Unnecessary Scope: You’re testing the controller, not the database or unrelated beans.
  • Complexity: Debugging failures becomes a needle-in-a-haystack hunt across layers.

@SpringBootTest is great for integration tests where you need the whole app, but for controller testing? It’s overkill. You want focus, speed, and just enough realism – without the baggage.

The Sweet Spot: @WebMvcTest

Enter @WebMvcTest – the go-to solution for testing Spring Boot controllers.

This annotation loads only the web layer (controllers, filters, and MVC infrastructure) while mocking the rest of your app. It’s fast, focused, and – crucially – preserves HTTP semantics through Spring’s MockMvc.

Here’s why it’s the best choice:

1. Mock HTTP Semantics

With MockMvc, you simulate real HTTP requests – GET, POST, PUT, DELETE, you name it.

You’re not calling methods directly; you’re hitting endpoints like a client would.

This lets you verify:

  • URL Mappings: Does /api/users route correctly?
  • Request Payloads: Are JSON inputs deserialized properly?
  • Response Bodies: Is the output what you expect?

2. Status Codes and Headers

MockMvc gives you full control to check HTTP-specific details:

  • Status: expect(status().isOk()) or expect(status().isForbidden()).
  • Headers: expect(header().string("X-Custom-Header", "value")).
  • Content Type: expect(content().contentType(MediaType.APPLICATION_JSON)).

No more guessing if your controller sets a 201 Created or forgets a header – @WebMvcTest has you covered.

3. Security Testing

Spring Security enabled?

@WebMvcTest integrates seamlessly. Add .with(user("testuser").roles("USER")) to your request and test:

Authentication: Does the endpoint reject unauthenticated calls?
Authorization: Are roles like @PreAuthorize("hasRole('ADMIN')") enforced?

You can’t do that with a plain unit test—and @SpringBootTest makes it painfully slow.

4. Speed and Focus

By loading only the MVC layer and mocking dependencies (e.g., services with @MockitoBean), @WebMvcTest keeps tests lightweight. No database connections, no unrelated beans – just the controller and its HTTP behavior.

A Quick Example

Let’s see @WebMvcTest in action. Imagine a simple controller:

Here’s how to test it with @WebMvcTest:

What’s happening here?

  • @WebMvcTest loads only UserController and MVC components.
  • @MockitoBean mocks UserService, keeping the test isolated.
  • MockMvc sends a GET request to /api/users/1 and checks the status, content type, and JSON response.

Fast, focused, and HTTP-aware. No full app startup, no bypassing the web layer.

Tips for @WebMvcTest Success

  • Mock Dependencies: Use @MockBean for services or repositories your controller needs.
  • Security Setup: Add @WithMockUser or custom SecurityMockMvcRequestPostProcessors for auth tests.
  • Error Handling: Test 400, 404, or 500 responses with invalid inputs or mock failures.
  • Keep It Lean: Avoid loading unnecessary controllers by specifying the target class (e.g., @WebMvcTest(UserController.class)).

Wrap-Up: Test Smarter, Not Harder

Testing Spring Boot controllers doesn’t have to be a compromise.

Plain unit tests with Mockito strip away HTTP semantics, leaving gaps in your coverage. @SpringBootTest drowns you in overhead you don’t need. @WebMvcTest hits the sweet spot – mock HTTP, real endpoints, and just enough context to test what matters: status codes, headers, security, and responses.

Next time you write a controller test, skip the extremes and reach for @WebMvcTest. Your test suite will thank you – and so will your team.

Want to dive deeper into Spring Boot testing? The Testing Spring Boot Applications Masterclass covers this and more – advanced techniques, best practices, and real-world examples. Ready to level up?

Become a more productive and confident Spring Boot developer.

Joyful testing,

Phil

>