Spring Boot Testing
@SpringBootTest
@WebMvcTest
@DataJpaTest
Unit Testing

SpringBootTest Vs WebMvcTest DataJpaTest service unit tests, what is the best?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

There is no single "best" Spring Boot test annotation for every case. The right choice depends on which layer you want to verify and how much of the application context you actually need.

Use @SpringBootTest for Full Integration

@SpringBootTest starts a full Spring application context. That makes it the broadest and heaviest option. Use it when the behavior depends on multiple layers working together, such as controllers, services, repositories, configuration, and external integrations.

java
1@SpringBootTest
2class OrderFlowIntegrationTest {
3
4    @Autowired
5    private OrderService orderService;
6
7    @Test
8    void createsOrderAndPersistsIt() {
9        Order order = orderService.create("book", 2);
10        assertNotNull(order.getId());
11    }
12}

This style gives realistic coverage, but it is slower because Spring builds most of the app. If every test uses @SpringBootTest, your suite becomes expensive and harder to maintain.

Use @WebMvcTest for Controller Behavior

@WebMvcTest loads the MVC layer, not the full app. It is designed for controller testing: request mapping, validation, serialization, status codes, and response bodies.

java
1@WebMvcTest(OrderController.class)
2class OrderControllerTest {
3
4    @Autowired
5    private MockMvc mockMvc;
6
7    @MockBean
8    private OrderService orderService;
9
10    @Test
11    void returnsCreatedStatus() throws Exception {
12        when(orderService.create("book", 2))
13            .thenReturn(new Order(1L, "book", 2));
14
15        mockMvc.perform(post("/orders")
16                .contentType(MediaType.APPLICATION_JSON)
17                .content("""
18                    {"name":"book","quantity":2}
19                    """))
20            .andExpect(status().isCreated());
21    }
22}

This test is faster because it isolates the web layer. The service is mocked, so the test tells you whether the controller behaves correctly, not whether the whole system works end to end.

Use @DataJpaTest for Repositories

@DataJpaTest focuses on persistence. It configures JPA components and usually runs with an embedded database unless you override that behavior.

java
1@DataJpaTest
2class OrderRepositoryTest {
3
4    @Autowired
5    private OrderRepository orderRepository;
6
7    @Test
8    void findsOrdersByName() {
9        orderRepository.save(new Order(null, "book", 2));
10
11        List<Order> result = orderRepository.findByName("book");
12
13        assertEquals(1, result.size());
14    }
15}

This is the right place to test custom queries, entity mappings, and repository behavior. It is much narrower than @SpringBootTest, which is why it is usually faster and easier to debug.

Service Tests Are Often Plain Unit Tests

For service logic, the best default is often not a Spring test at all. If the service mostly coordinates collaborators, instantiate it directly and mock its dependencies.

java
1@ExtendWith(MockitoExtension.class)
2class OrderServiceTest {
3
4    @Mock
5    private OrderRepository orderRepository;
6
7    @InjectMocks
8    private OrderService orderService;
9
10    @Test
11    void rejectsInvalidQuantity() {
12        assertThrows(IllegalArgumentException.class,
13            () -> orderService.create("book", 0));
14    }
15}

This kind of test is fast and precise. It verifies business rules without paying for Spring startup. That is usually the best place for core service logic.

Build a Testing Pyramid, Not a Single Strategy

The practical answer is usually:

  • many plain unit tests for services and utility classes
  • targeted @WebMvcTest tests for controllers
  • targeted @DataJpaTest tests for repository behavior
  • fewer @SpringBootTest tests for full integration scenarios

That balance gives you both speed and confidence. If you push everything into full-context tests, the suite becomes slower than necessary. If you use only unit tests, you miss wiring problems between layers.

How to Choose Quickly

Ask one question: what am I trying to prove

If the answer is "my controller returns the right HTTP response," use @WebMvcTest. If it is "my query works," use @DataJpaTest. If it is "this business rule is correct," use a plain unit test. If it is "the whole flow works together," use @SpringBootTest.

That framing is more useful than asking which annotation is universally best, because each one is designed for a different boundary.

Common Pitfalls

  • Using @SpringBootTest for simple business-rule tests. That makes the suite slower with no real benefit.
  • Expecting @WebMvcTest to load repositories and services automatically. It only loads the web slice, so collaborators usually need mocks.
  • Testing JPA queries in a plain unit test. Repository behavior should be exercised in a data-layer test.
  • Mixing too many concerns into one test class. A controller test should not also be a persistence test.
  • Calling everything a unit test. Full-context Spring tests are integration tests, and treating them as unit tests hides their real cost.

Summary

  • '@SpringBootTest is for full integration and costs the most.'
  • '@WebMvcTest is for controller and HTTP behavior.'
  • '@DataJpaTest is for repositories, mappings, and queries.'
  • Service logic is often best tested with plain unit tests and mocked dependencies.
  • The strongest test strategy mixes narrow fast tests with a smaller number of broad integration tests.

Course illustration
Course illustration

All Rights Reserved.