Error on test case annotated with @WithMockUser on application extend Spring Security User class
Tags: spring, springsecurity
Published: 2024-01-31 19:19:44

## Notice

I wrote this article and was originally published on [Qiita](https://qiita.com/saladlam/items/b9c9c0ad55c902b694f6) on 19 September 2022.

---

# Symptom

- application extends **org.springframework.security.core.userdetails.User** class to store extra user attributes, for example **CustomUser**
- test case annonated with @WithMockUser in order to create mock user
- in some place of application, reading user attribute likes below

```java
CustomUser user = (CustomUser) SecurityContextHolder.getContext().getAuthentication().getCredentials();
String name = user.getName();
```

When the test case run, following error shown

```
problem: cannot cast from User to CustomUser
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.ClassCastException: class org.springframework.security.core.userdetails.User cannot be cast to class CustomUser
```

# Reason

When a test case starts, a **org.springframework.security.core.userdetails.User** instance is built by class **org.springframework.security.test.context.support.WithMockUserSecurityContextFactory** with properties specified by @WithMockUser annotation and then stored into SecurityContext. In some places of the application, object casting to custom user instances is performed in order to access the custom properties. Since instance needs casting is the parent class of custom user class, so casting operations will fail.

# Solution

You need to modify @WithMockUser annotation class and mock user builder class **org.springframework.security.test.context.support.WithMockUserSecurityContextFactory**. Since these two classes are not extendable, you should create two new classes, copy code and then make necessary modifications. Following is the code that came from [my example application](https://github.com/saladlam/spring-noticeboard).

```java:WithMockCustomUser.java
package info.saladlam.example.spring.noticeboard.support;

import org.springframework.core.annotation.AliasFor;
import org.springframework.security.test.context.support.TestExecutionEvent;
import org.springframework.security.test.context.support.WithSecurityContext;

import java.lang.annotation.*;

/**
 * Copy from org.springframework.security.test.context.support.WithMockUser and customize.
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@WithSecurityContext(factory = WithMockUserSecurityContextFactory.class)
public @interface WithMockCustomUser {

	String value() default "user";

	String username() default "";

	String[] roles() default { "USER" };

	String[] authorities() default {};

	String password() default "password";

	String name() default "";

	String email() default "";

	@AliasFor(annotation = WithSecurityContext.class)
	TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;

}
```

```java:WithMockUserSecurityContextFactory.java
package info.saladlam.example.spring.noticeboard.support;

import info.saladlam.example.spring.noticeboard.entity.CustomUser;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Copy from org.springframework.security.test.context.support.WithMockUserSecurityContextFactory and customize.
 */
final class WithMockUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {

	@Override
	public SecurityContext createSecurityContext(WithMockCustomUser withUser) {
		String username = StringUtils.hasLength(withUser.username()) ? withUser.username() : withUser.value();
		Assert.notNull(username, () -> withUser + " cannot have null username on both username and value properties");
		List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
		for (String authority : withUser.authorities()) {
			grantedAuthorities.add(new SimpleGrantedAuthority(authority));
		}
		if (grantedAuthorities.isEmpty()) {
			for (String role : withUser.roles()) {
				Assert.isTrue(!role.startsWith("ROLE_"), () -> "roles cannot start with ROLE_ Got " + role);
				grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role));
			}
		}
		else if (!(withUser.roles().length == 1 && "USER".equals(withUser.roles()[0]))) {
			throw new IllegalStateException("You cannot define roles attribute " + Arrays.asList(withUser.roles())
					+ " with authorities attribute " + Arrays.asList(withUser.authorities()));
		}
		User principal = new CustomUser(username, withUser.password(), true, true, true, true, grantedAuthorities, withUser.name(), withUser.email());
		Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(),
				principal.getAuthorities());
		SecurityContext context = SecurityContextHolder.createEmptyContext();
		context.setAuthentication(authentication);
		return context;
	}

}
```

And then when writing the test case, your new annotation should be used.

```java
	@WithMockCustomUser(username = "user1", authorities = {"USER"}, name = "First Last")
	public void userAction1() throws Exception {
        ...
    }
```