skip to content

When would you use @WithUserDetails instead of @WithMockUser?

level: middleimportance: must knowfreq 62%

answer

  1. MockUser = fabricated, UserDetails = real loader
  2. calls loadUserByUsername
  3. custom principal cast works only with @WithUserDetails
  4. userDetailsServiceBeanName picks the bean
  5. user must be loadable / seeded

basics

~10 s

@WithMockUser fabricates a fake user from annotation attributes. @WithUserDetails loads a real user through your UserDetailsService, so the test uses the same principal type, authorities, and fields your app produces.

solid answer

~40 s

Use @WithUserDetails when the mock principal isn't enough — when your code casts the principal to a custom UserDetails, reads domain fields (id, tenant, email), or relies on authorities computed by your loader. @WithUserDetails(value = "alice") calls your registered UserDetailsService.loadUserByUsername("alice") and puts the returned UserDetails into a UsernamePasswordAuthenticationToken, so the SecurityContext holds your real principal type. It needs that user to be loadable — typically seeded in the DB/test config, or served by an in-memory UserDetailsService bean. Pick the bean with userDetailsServiceBeanName when multiple exist. @WithMockUser stays best for generic authorization checks where you only care about username and roles, not a concrete principal.

code

java · 24 lines
java
@SpringBootTest
@AutoConfigureMockMvc
class OrderControllerTest {

    @Autowired MockMvc mvc;

    // Assumes a UserDetailsService bean loads "alice" -> AppUserPrincipal(id=42)
    @Test
    @WithUserDetails("alice")
    void usesRealPrincipalId() throws Exception {
        // controller does ((AppUserPrincipal) auth.getPrincipal()).getId()
        mvc.perform(get("/orders/mine"))
           .andExpect(status().isOk())
           .andExpect(jsonPath("$.ownerId").value(42));
    }

    @Test
    @WithUserDetails(value = "admin",
                     userDetailsServiceBeanName = "jdbcUserDetailsService")
    void picksSpecificUserDetailsServiceBean() throws Exception {
        mvc.perform(get("/admin/orders"))
           .andExpect(status().isOk());
    }
}

go deeper

for a junior

Know that @WithUserDetails loads a real user while @WithMockUser fakes one.

for a middle

Explain loadUserByUsername delegation, userDetailsServiceBeanName, and the custom-principal cast benefit.

for a senior

Reason about slice vs full context, seeding, transaction/lazy-loading timing, and when a custom factory beats @WithUserDetails.

for a principal

Weigh test isolation/fidelity trade-offs: fabricated principal vs real loader vs custom factory across module boundaries.

## The core distinction Both annotations populate the `SecurityContext` before a test, but they build the principal differently: - **`@WithMockUser`** — fabricates a `org.springframework.security.core.userdetails.User` purely from annotation attributes. It never touches your application's user-loading logic. - **`@WithUserDetails`** — delegates to a **`UserDetailsService`** bean in the Spring context, calling `loadUserByUsername(...)` and using the returned `UserDetails` as the principal. This is your *real* principal — same concrete class, same authorities, same custom fields. ## Why the difference matters Many applications use a **custom `UserDetails`** implementation (e.g. `AppUserPrincipal implements UserDetails`) that carries a user id, tenant, display name, etc. Controllers/services then do: ```java var principal = (AppUserPrincipal) authentication.getPrincipal(); Long userId = principal.getId(); ``` With `@WithMockUser` the principal is a plain framework `User`, so that cast throws `ClassCastException` and the field isn't there. `@WithUserDetails` gives you the genuine principal, so casts and field reads work exactly as in production. ## Attributes of @WithUserDetails - `value` (alias, default `"user"`) — the username passed to `loadUserByUsername`. - `userDetailsServiceBeanName` — the **name of the `UserDetailsService` bean** to use when several exist in the context; omit it when there's exactly one. - `setupBefore` — same `TestExecutionEvent` timing knob as other annotations (default `TEST_METHOD`). ## Prerequisites / gotchas - The **user must actually load**. Either seed it (e.g. `@Sql` inserts, a `TestEntityManager`, an in-memory `InMemoryUserDetailsManager` bean, or your JDBC/JPA `UserDetailsService` reading test data) or the test fails with `UsernameNotFoundException`. - A `UserDetailsService` **bean must exist** in the test's `ApplicationContext`. In a `@WebMvcTest` slice, the persistence layer isn't loaded, so you often `@MockBean`/`@TestConfiguration` a `UserDetailsService`, or use `@SpringBootTest` for a full context. - Loading happens inside the test's transaction/context; if your loader is `@Transactional` and lazy-loads authorities, ensure the data is available at load time. The default `setupBefore = TEST_METHOD` runs *after* `@BeforeEach`, which is usually what you want so setup data exists. - If you need a fully arbitrary principal that doesn't correspond to any loadable user, prefer a **custom `@WithSecurityContext` factory** over forcing `@WithUserDetails`. ## When to use which - **`@WithMockUser`** — generic authz assertions: 'a USER is forbidden from /admin', 'an ADMIN gets 200'. Fast, no DB, no bean. - **`@WithUserDetails`** — behavior that depends on the concrete principal: custom `UserDetails` casts, id/tenant lookups, or verifying authorities exactly as your loader computes them.

  • You use @WithUserDetails in a @WebMvcTest and get UsernameNotFoundException or no UserDetailsService bean. Why?
    @WebMvcTest loads only the web slice, not your persistence layer or its UserDetailsService. You must provide a UserDetailsService bean (e.g. a @TestConfiguration with InMemoryUserDetailsManager) and ensure the username is loadable, or use @SpringBootTest for a full context.
  • The user exists but authorities differ from production. What's a likely cause?
    The test UserDetailsService (or seeded data) computes authorities differently than the real one. @WithUserDetails only reflects whatever your loader returns, so verify the bean actually in the test context and the seeded roles.

saying these in an interview costs you the question

  • Thinking @WithUserDetails takes the same attributes as @WithMockUser (roles/authorities) — it doesn't; it loads them
  • Expecting a custom UserDetails cast to work under @WithMockUser
  • Forgetting that a UserDetailsService bean and a loadable user must exist
  • Believing @WithUserDetails performs a password check / real authentication

context