skip to content

Compare @WithMockUser, @WithUserDetails, and @WithSecurityContext. When do you need each?

level: seniorimportance: should knowfreq 55%

answer

  1. same listener, different Authentication source
  2. MockUser=literal attrs, generic User principal
  3. WithUserDetails=loadUserByUsername, real principal
  4. WithSecurityContext=custom factory, any Authentication
  5. need custom principal type? not MockUser

basics

~20 s

@WithMockUser fabricates a simple principal from annotation attributes. @WithUserDetails loads a real principal via your UserDetailsService bean by username, so the principal is your actual UserDetails type. @WithSecurityContext is the extensible base that lets you build a fully custom SecurityContext/Authentication via a factory.

solid answer

~40 s

All three seed the SecurityContext through the same WithSecurityContextTestExecutionListener but differ in how the principal is built. @WithMockUser creates a UsernamePasswordAuthenticationToken from literal attributes (username/roles/authorities) — fast, but the principal is a generic User, not your domain type. @WithUserDetails("alice") calls your registered UserDetailsService.loadUserByUsername, so the principal is your real UserDetails implementation with real authorities — use it when the controller casts to a custom principal or when you want authorities to come from the same source as production; it needs the user to exist (often seeded in a test @Configuration). @WithSecurityContext is the meta-annotation both are built on: you write a custom annotation plus a SecurityContextFactory that returns any Authentication (e.g. a JwtAuthenticationToken or OAuth2 principal) — use it for bespoke authentication types the built-ins don't cover.

code

java · 23 lines
java
// Custom principal via @WithUserDetails (real UserDetailsService)
@Test
@WithUserDetails("[email protected]")
void usesRealPrincipal() throws Exception {
  mvc.perform(get("/api/tenant").with(csrf()))
     .andExpect(status().isOk());
}

// Fully custom Authentication via @WithSecurityContext
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockTenantSecurityContextFactory.class)
@interface WithMockTenant { String user(); long tenantId(); }

class WithMockTenantSecurityContextFactory
    implements WithSecurityContextFactory<WithMockTenant> {
  @Override public SecurityContext createSecurityContext(WithMockTenant a) {
    var ctx = SecurityContextHolder.createEmptyContext();
    var principal = new TenantPrincipal(a.user(), a.tenantId());
    ctx.setAuthentication(new UsernamePasswordAuthenticationToken(
        principal, null, List.of(new SimpleGrantedAuthority("ROLE_USER"))));
    return ctx;
  }
}

go deeper

for a junior

Know @WithMockUser is the simple default; the others exist for real/custom principals.

for a middle

Explain that @WithUserDetails calls loadUserByUsername and yields your real principal type.

for a senior

Build a custom @WithSecurityContext factory and pick correctly among the three based on principal type and authority source.

for a principal

Set team conventions: prefer @WithUserDetails/custom factories so tested authorities match production mapping; avoid authority drift from hand-written @WithMockUser roles.

## Shared mechanism All three are (or are built on) `@WithSecurityContext` and are processed by the same `WithSecurityContextTestExecutionListener`, which builds a `SecurityContext` before the test method and clears it after. They differ only in **how the `Authentication` inside that context is produced**. ## @WithMockUser - Source: literal annotation attributes (`username`, `roles`, `authorities`, `password`). - Principal: a Spring Security `User` (implements `UserDetails`) inside a `UsernamePasswordAuthenticationToken`. - Pros: zero setup, no beans required, fastest. - Cons: principal is generic. If your controller does `((MyUserPrincipal) auth.getPrincipal()).getTenantId()` it will `ClassCastException`. Authorities are hand-declared, so they can drift from what production actually grants. ## @WithUserDetails - `@WithUserDetails("alice")` — looks up bean of type `UserDetailsService` (by default a bean named per `userDetailsServiceBeanName`, else by type) and calls `loadUserByUsername("alice")`. - Principal: **your real `UserDetails`** with the authorities your production logic assigns. - Requires the user to be resolvable — commonly you provide an in-memory or test `UserDetailsService` bean, or seed the DB. If not found, the test fails with `UsernameNotFoundException`. - `setupBefore` attribute (like `@WithMockUser`) controls whether the context is set before or after `@BeforeEach`. - Use when: custom principal type, or you want authorities sourced identically to production, or you're testing logic that depends on real user fields. ## @WithSecurityContext - The extension point. You create a **composed annotation** meta-annotated with `@WithSecurityContext(factory = MyFactory.class)`, and implement `WithSecurityContextFactory<YourAnnotation>` whose `createSecurityContext(annotation)` returns any `SecurityContext`. - Lets you seed **arbitrary** `Authentication` types: `JwtAuthenticationToken`, `OAuth2AuthenticationToken`, a custom token carrying tenant/claims, etc. - Use when the built-ins can't express your principal, and you want it declaratively/reusably (vs. the imperative `jwt()`/`user(UserDetails)` post-processors which do similar things per-request). ## Choosing - Simple role check, don't care about principal type → **@WithMockUser**. - Need the real domain principal / production authority mapping → **@WithUserDetails**. - Need a non-standard `Authentication` (JWT/OAuth2/custom) declaratively → **@WithSecurityContext** (custom annotation) — or use `jwt()`/`user(UserDetails)` post-processors imperatively. ## Gotchas 1. `@WithUserDetails` needs the `UserDetailsService` bean in the test's ApplicationContext; in a sliced `@WebMvcTest` you may have to provide one. 2. `@WithMockUser` roles auto-prefix `ROLE_`; `@WithUserDetails` uses whatever your service returns — no re-prefixing. 3. All three seed the context but still don't add a CSRF token — mutating MockMvc requests need `.with(csrf())`. 4. The factory in `@WithSecurityContext` must be a no-arg-constructable class (Spring can autowire it if `WithSecurityContextFactory` is a bean via `@TestExecutionListeners` support in newer versions, but the classic contract is a plain factory). 5. `@WithUserDetails` transaction/lazy-loading: if `loadUserByUsername` touches lazy JPA associations outside a transaction it can fail — keep the UserDetails self-contained.

  • Your controller casts the principal to a custom UserDetails subtype and the @WithMockUser test throws ClassCastException. Fix?
    @WithMockUser injects a generic Spring User. Switch to @WithUserDetails so your real UserDetailsService returns your subtype, or use SecurityMockMvcRequestPostProcessors.user(myUserDetails), or a custom @WithSecurityContext factory that builds the exact principal type.
  • What does @WithUserDetails need in the test context that @WithMockUser does not?
    A UserDetailsService bean that can resolve the given username (loadUserByUsername). In a sliced @WebMvcTest you often must supply one; @WithMockUser needs no beans since it fabricates the principal from attributes.

context