← Spring Boot®: applications, contracts and operations
08 / 10 · 60 MIN

Test scope and integration evidence

Interpret what each test scope demonstrates, preserve access controls, and distinguish local rollback from effects committed on other threads.

Choose the evidence scope

A unit test can examine a rule with controlled dependencies. A WebMvcTest slice selects MVC infrastructure and relevant components instead of indiscriminately loading the entire application. SpringBootTest can load a broader context; the chosen web environment determines whether a real server exists.

Write the intended conclusion before reading the result. A correct JSON response in a slice with a mocked service does not establish commit, SQL compatibility or access to an external API. Each replacement limits evidence scope even when it makes the test fast and useful.

Configure an explicit slice

WebMvcTest can supply MockMvc and security support when relevant dependencies are present. Importing the SecurityConfiguration under evaluation makes policy explicit. MockitoBean supplies a controlled collaborator to the controller; configured mock behavior is part of the result’s premises.

In Boot 4, use modules and packages matching the selected version. The example imports WebMvcTest from org.springframework.boot.webmvc.test.autoconfigure and MockitoBean from org.springframework.test.context.bean.override.mockito. Do not transfer old imports without checking the project’s API.

A simulated identity is not login

WithMockUser places an identity in the test security context. The user need not exist in the database and its password does not pass through real authentication. The authorities attribute can supply REPORT_WRITE literally; roles uses the ROLE_ prefix convention.

This is useful for comparing authorization decisions with controlled identities. It does not establish password verification, JWT signature validation or issuer availability. Associate those conclusions with their own evidence instead of inferring them from a controller authorized with a simulated principal.

Isolate the reason for rejection

A 403 rejection can result from insufficient authority or CSRF, among other configured causes. To compare REPORT_WRITE with REPORT_READ on a protected POST, supply both with a valid token. Add a comparison with sufficient authority and a missing or invalid token.

The check should also observe effects: on rejection before the controller, the mocked service should not be called. A positive case prevents an always-blocking policy from appearing correct. Disabling filters can suit another scope but no longer supports conclusions about those controls.

Real server and isolated data

SpringBootTest with MOCK does not normally start an embedded server. RANDOM_PORT starts a server on an assigned port and permits real HTTP requests in that environment. This adds an execution boundary: client and server work on separate threads and can use separate transactions.

Transactional on the test method does not automatically undo server commits. If the next comparison uses the same database, it needs explicit isolation and cleanup. Changing the port does not clean records. Also distinguish this local server from a real path through an external gateway, TLS and load balancer.

Observe synchronization and commit

In an ORM test, persist can change the context without immediately sending all SQL. If the test ends in rollback before relevant synchronization, a database constraint may never be observed. An explicit flush at the appropriate point permits checking that behavior without being equivalent to final transaction commit.

Test-managed transaction state is thread-bound. A preemptive timeout executing the action on another thread can leave committed effects outside expected rollback. When analyzing a green test, ask which SQL was observed, which thread ran it and which effects still need cleanup.

Interpret the example matrix

The example supplies the ReportService contract, a controller invoking it and three comparisons with the previous lesson’s policy. The service is replaced in each test. permitsWriter combines REPORT_WRITE and csrf(); rejectsReader retains csrf() and changes authority; rejectsMissingCsrf keeps REPORT_WRITE and omits the token.

This distinction associates each expectation with the relevant control. The success string comes from the mock and certifies neither publication nor persistence. Assessment asks for interpretations, limits and next steps from the excerpt; it requires no JUnit execution, server startup or code writing.

// Separate files in package com.acme.reports.
// Uses SecurityConfiguration from the preceding lesson.
// Test dependencies: Boot 4.1.1 MVC test support, Spring Security test,
// JUnit Jupiter and Mockito. Read the snippet; execution is not assessed.
// ReportService.java
package com.acme.reports;
public interface ReportService { String publish(); }

// ReportController.java
package com.acme.reports;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ReportController {
  private final ReportService service;
  public ReportController(ReportService service) { this.service = service; }
  @PostMapping(path = "/reports/publish", produces = "text/plain")
  public String publish() { return service.publish(); }
}

// ReportControllerTests.java
package com.acme.reports;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(ReportController.class)
@Import(SecurityConfiguration.class)
class ReportControllerTests {
  @Autowired MockMvc mvc;
  @MockitoBean ReportService service;

  @Test @WithMockUser(authorities = "REPORT_WRITE")
  void permitsWriter() throws Exception {
    when(service.publish()).thenReturn("accepted-in-test");
    mvc.perform(post("/reports/publish").with(csrf()))
        .andExpect(status().isOk())
        .andExpect(content().string("accepted-in-test"));
    verify(service).publish();
  }

  @Test @WithMockUser(authorities = "REPORT_READ")
  void rejectsReader() throws Exception {
    mvc.perform(post("/reports/publish").with(csrf()))
        .andExpect(status().isForbidden());
    verifyNoInteractions(service);
  }

  @Test @WithMockUser(authorities = "REPORT_WRITE")
  void rejectsMissingCsrf() throws Exception {
    mvc.perform(post("/reports/publish"))
        .andExpect(status().isForbidden());
    verifyNoInteractions(service);
  }
}
IN PRACTICE

Compare the three methods without executing them. The first combines sufficient authority and a valid token; the second changes only authority; the third retains sufficient authority and omits the token. The accepted-in-test return is defined by the mock and does not prove that a report was stored.

Common pitfalls

Interpreting WithMockUser as login; disabling filters to obtain green results; attributing every 403 to authority; assuming server rollback from Transactional on the client; confusing flush with commit.

Related topics: Security and authorization boundaries · Persistence and transaction boundaries · Packaging, delivery and operational acceptance

Take this idea with you

A result only supports the scope exercised. Make replacements, active controls, threads and transactions explicit before concluding that complete behavior is validated.

Create account

Reference: Testing Spring Boot applications · Spring Boot 4.1.1 / Spring Framework 7.0.9 / Java 25