How do you run an MVC slice/integration test as an authenticated user without performing a real login? Explain @WithMockUser.
answer
- seeds SecurityContext before test, clears after
- default user/ROLE_USER, roles auto-prefix ROLE_
- WithSecurityContextTestExecutionListener
- no real auth, no filter chain
- still need .with(csrf()) for POST
basics
~20 sAnnotate the test method (or class) with @WithMockUser from spring-security-test. Before the test runs it seeds the SecurityContext with a fake authenticated user (default name 'user', role 'ROLE_USER'), so security checks pass without a real login.
solid answer
~40 s@WithMockUser (in spring-security-test) populates the SecurityContextHolder with a UsernamePasswordAuthenticationToken before the test body executes, and clears it afterward. Attributes let you customize identity: username/value for the name, roles=... which auto-prefix ROLE_, or authorities=... for raw, unprefixed authorities (you can't use both). Because it runs via a JUnit test-execution listener (WithSecurityContextTestExecutionListener, auto-registered by @SpringBootTest / spring-security's test setup), no password check, no filter chain, no HTTP round-trip happens. It works for any test that reads the SecurityContext — including MockMvc method-security tests. Pair it with .with(csrf()) on mutating MockMvc requests since it doesn't add a CSRF token. Put it on the class to apply to every method, or on a method to override.
code
java · 19 lines@WebMvcTest(AccountController.class)
class AccountControllerTest {
@Autowired MockMvc mvc;
@Test
@WithMockUser // name "user", authority ROLE_USER
void getProfile_authenticated_returns200() throws Exception {
mvc.perform(get("/api/me"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "admin", roles = {"ADMIN"}) // ROLE_ADMIN
void deleteUser_asAdmin_ok() throws Exception {
mvc.perform(delete("/api/users/42").with(csrf())) // CSRF still required
.andExpect(status().isNoContent());
}
}go deeper
Know it makes the test run as a logged-in user and roles get ROLE_ prefixed; remember csrf() for POST.
Explain the listener mechanism, roles-vs-authorities, class-vs-method placement, and that no real login happens.
Contrast with @WithUserDetails/@WithSecurityContext for custom principals; know setupBefore and when it isn't the right tool (testing the actual login path).
Reason about composed meta-annotations for reusable identities and the boundary between context-seeding tests and full filter-chain/authentication-flow tests.
## What it is `@WithMockUser` is an annotation from the **spring-security-test** module (`org.springframework.security.test.context.support.WithMockUser`). It sets up a fully-populated `SecurityContext` *before* your test method body runs, so any code that calls `SecurityContextHolder.getContext().getAuthentication()` — or any authorization rule like `@PreAuthorize`, `.authenticated()`, method security — sees a logged-in principal. It removes the context after the test. ## The mechanism Spring Security registers a `WithSecurityContextTestExecutionListener` (a JUnit `TestExecutionListener`). When it sees a `@WithMockUser` (or any `@WithSecurityContext`-meta-annotated annotation) on the test method or class, it builds a `SecurityContext` containing a `UsernamePasswordAuthenticationToken` and stores it in the `SecurityContextHolder` before the test, then clears it after. This listener is auto-registered when you use `@SpringBootTest`, `@WebMvcTest`, or otherwise import Spring's test context — you do **not** wire it manually. Key point: **no real authentication happens.** There is no password verification, no `AuthenticationManager` call, no HTTP request, no security filter chain. It's a shortcut that directly seeds the context. ## Attributes - `value` / `username` — the principal name. Default is `"user"`. - `password` — default `"password"` (rarely matters). - `roles` — e.g. `roles = {"ADMIN"}` becomes authority `ROLE_ADMIN`. Default is `{"USER"}` → `ROLE_USER`. **Spring auto-prefixes `ROLE_`; if you write `roles = "ROLE_ADMIN"` you get the illegal `ROLE_ROLE_ADMIN` and an exception.** - `authorities` — raw authorities with **no** prefix, e.g. `authorities = {"read", "ROLE_ADMIN"}`. You may set `roles` OR `authorities`, never both (throws). ## Scope / placement - On a **method** → applies to that test only. - On the **class** → applies to every test method (each can override with its own annotation). - On a **custom composed annotation** (meta-annotation) → build reusable `@WithMockAdmin`. ## Common gotchas 1. **CSRF**: `@WithMockUser` authenticates you but does NOT add a CSRF token. A `POST`/`PUT`/`DELETE` through MockMvc against a default (CSRF-enabled) config returns 403 unless you add `.with(csrf())`. 2. **roles vs authorities prefix confusion** — see above; the #1 mistake. 3. **It seeds the context, not an HTTP session** — good for method security and controllers alike, but it isn't testing your real login/filter path. To test the actual authentication flow you need a real request (e.g. `formLogin()` or `httpBasic()` post-processors), not `@WithMockUser`. 4. **Principal type** — the principal is a `User`/String, not your custom `UserDetails`. If your controller casts to a custom principal type, use `@WithUserDetails` or `user(...).password(...)` / a custom `@WithSecurityContext` instead. 5. **setupBefore** — `@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)` seeds the context *after* `@Before`/`@BeforeEach` methods instead of before them (the default is `TEST_METHOD`, i.e. before setup). Useful when setup code itself needs the user, or must NOT. ## When to use Default choice for controller/method-security tests where you just need *some* authenticated user with given roles and don't care about the exact `UserDetails` instance or a real login round-trip.
- Why does a POST in a @WithMockUser test return 403 even though the user is authenticated?@WithMockUser only seeds authentication; it does not add a CSRF token. With Spring Security's default CSRF protection, mutating requests need a valid token, so you must add SecurityMockMvcRequestPostProcessors.csrf() (e.g. .with(csrf())).
- What's the difference between roles = {"ADMIN"} and authorities = {"ADMIN"}?roles auto-prefixes ROLE_, giving authority ROLE_ADMIN (matches hasRole('ADMIN')). authorities is taken verbatim with no prefix, giving authority ADMIN (matches hasAuthority('ADMIN')). You can set one or the other, not both.