You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
696 lines
20 KiB
696 lines
20 KiB
[[test-method]] |
|
= Testing Method Security |
|
|
|
This section demonstrates how to use Spring Security's Test support to test method based security. |
|
We first introduce a `MessageService` that requires the user to be authenticated in order to access it. |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
public class HelloMessageService implements MessageService { |
|
|
|
@PreAuthorize("authenticated") |
|
public String getMessage() { |
|
Authentication authentication = SecurityContextHolder.getContext() |
|
.getAuthentication(); |
|
return "Hello " + authentication; |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
class HelloMessageService : MessageService { |
|
@PreAuthorize("authenticated") |
|
fun getMessage(): String { |
|
val authentication: Authentication = SecurityContextHolder.getContext().authentication |
|
return "Hello $authentication" |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
The result of `getMessage` is a String saying "Hello" to the current Spring Security `Authentication`. |
|
An example of the output is displayed below. |
|
|
|
[source,text] |
|
---- |
|
Hello org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER |
|
---- |
|
|
|
[[test-method-setup]] |
|
== Security Test Setup |
|
|
|
Before we can use Spring Security Test support, we must perform some setup. An example can be seen below: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@ExtendWith(SpringExtension.class) // <1> |
|
@ContextConfiguration // <2> |
|
public class WithMockUserTests { |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@ExtendWith(SpringExtension.class) |
|
@ContextConfiguration |
|
class WithMockUserTests { |
|
---- |
|
====== |
|
|
|
This is a basic example of how to setup Spring Security Test. The highlights are: |
|
|
|
<1> `@ExtendWith` instructs the spring-test module that it should create an `ApplicationContext`. For additional information, refer to the {spring-framework-reference-url}testing.html#testcontext-junit-jupiter-extension[Spring reference]. |
|
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the {spring-framework-reference-url}testing.html#spring-testing-annotation-contextconfiguration[Spring Reference] |
|
|
|
NOTE: Spring Security hooks into Spring Test support using the `WithSecurityContextTestExecutionListener` which will ensure our tests are ran with the correct user. |
|
It does this by populating the `SecurityContextHolder` prior to running our tests. |
|
If you are using reactive method security, you will also need `ReactorContextTestExecutionListener` which populates `ReactiveSecurityContextHolder`. |
|
After the test is done, it will clear out the `SecurityContextHolder`. |
|
If you only need Spring Security related support, you can replace `@ContextConfiguration` with `@SecurityTestExecutionListeners`. |
|
|
|
Remember we added the `@PreAuthorize` annotation to our `HelloMessageService` and so it requires an authenticated user to invoke it. |
|
If we ran the following test, we would expect the following test will pass: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test(expected = AuthenticationCredentialsNotFoundException.class) |
|
public void getMessageUnauthenticated() { |
|
messageService.getMessage(); |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test(expected = AuthenticationCredentialsNotFoundException::class) |
|
fun getMessageUnauthenticated() { |
|
messageService.getMessage() |
|
} |
|
---- |
|
====== |
|
|
|
[[test-method-withmockuser]] |
|
== @WithMockUser |
|
|
|
The question is "How could we most easily run the test as a specific user?" |
|
The answer is to use `@WithMockUser`. |
|
The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER". |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test |
|
@WithMockUser |
|
public void getMessageWithMockUser() { |
|
String message = messageService.getMessage(); |
|
... |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test |
|
@WithMockUser |
|
fun getMessageWithMockUser() { |
|
val message: String = messageService.getMessage() |
|
// ... |
|
} |
|
---- |
|
====== |
|
|
|
Specifically the following is true: |
|
|
|
* The user with the username "user" does not have to exist since we are mocking the user |
|
* The `Authentication` that is populated in the `SecurityContext` is of type `UsernamePasswordAuthenticationToken` |
|
* The principal on the `Authentication` is Spring Security's `User` object |
|
* The `User` will have the username of "user", the password "password", and a single `GrantedAuthority` named "ROLE_USER" is used. |
|
|
|
Our example is nice because we are able to leverage a lot of defaults. |
|
What if we wanted to run the test with a different username? |
|
The following test would run with the username "customUser". Again, the user does not need to actually exist. |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test |
|
@WithMockUser("customUsername") |
|
public void getMessageWithMockUserCustomUsername() { |
|
String message = messageService.getMessage(); |
|
... |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test |
|
@WithMockUser("customUsername") |
|
fun getMessageWithMockUserCustomUsername() { |
|
val message: String = messageService.getMessage() |
|
// ... |
|
} |
|
---- |
|
====== |
|
|
|
We can also easily customize the roles. |
|
For example, this test will be invoked with the username "admin" and the roles "ROLE_USER" and "ROLE_ADMIN". |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test |
|
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
|
public void getMessageWithMockUserCustomUser() { |
|
String message = messageService.getMessage(); |
|
... |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test |
|
@WithMockUser(username="admin",roles=["USER","ADMIN"]) |
|
fun getMessageWithMockUserCustomUser() { |
|
val message: String = messageService.getMessage() |
|
// ... |
|
} |
|
---- |
|
====== |
|
|
|
If we do not want the value to automatically be prefixed with ROLE_ we can leverage the authorities attribute. |
|
For example, this test will be invoked with the username "admin" and the authorities "USER" and "ADMIN". |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test |
|
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" }) |
|
public void getMessageWithMockUserCustomAuthorities() { |
|
String message = messageService.getMessage(); |
|
... |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test |
|
@WithMockUser(username = "admin", authorities = ["ADMIN", "USER"]) |
|
fun getMessageWithMockUserCustomUsername() { |
|
val message: String = messageService.getMessage() |
|
// ... |
|
} |
|
---- |
|
====== |
|
|
|
Of course it can be a bit tedious placing the annotation on every test method. |
|
Instead, we can place the annotation at the class level and every test will use the specified user. |
|
For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN". |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@ExtendWith(SpringExtension.class) |
|
@ContextConfiguration |
|
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
|
public class WithMockUserTests { |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@ExtendWith(SpringExtension.class) |
|
@ContextConfiguration |
|
@WithMockUser(username="admin",roles=["USER","ADMIN"]) |
|
class WithMockUserTests { |
|
---- |
|
====== |
|
|
|
If you are using JUnit 5's `@Nested` test support, you can also place the annotation on the enclosing class to apply to all nested classes. |
|
For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN" for both test methods. |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@ExtendWith(SpringExtension.class) |
|
@ContextConfiguration |
|
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
|
public class WithMockUserTests { |
|
|
|
@Nested |
|
public class TestSuite1 { |
|
// ... all test methods use admin user |
|
} |
|
|
|
@Nested |
|
public class TestSuite2 { |
|
// ... all test methods use admin user |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@ExtendWith(SpringExtension::class) |
|
@ContextConfiguration |
|
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"]) |
|
class WithMockUserTests { |
|
@Nested |
|
inner class TestSuite1 { // ... all test methods use admin user |
|
} |
|
|
|
@Nested |
|
inner class TestSuite2 { // ... all test methods use admin user |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. |
|
This is the equivalent of happening before JUnit's `@Before`. |
|
You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. |
|
|
|
[source,java] |
|
---- |
|
@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) |
|
---- |
|
|
|
|
|
[[test-method-withanonymoususer]] |
|
== @WithAnonymousUser |
|
|
|
Using `@WithAnonymousUser` allows running as an anonymous user. |
|
This is especially convenient when you wish to run most of your tests with a specific user, but want to run a few tests as an anonymous user. |
|
For example, the following will run withMockUser1 and withMockUser2 using <<test-method-withmockuser,@WithMockUser>> and anonymous as an anonymous user. |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@ExtendWith(SpringExtension.class) |
|
@WithMockUser |
|
public class WithUserClassLevelAuthenticationTests { |
|
|
|
@Test |
|
public void withMockUser1() { |
|
} |
|
|
|
@Test |
|
public void withMockUser2() { |
|
} |
|
|
|
@Test |
|
@WithAnonymousUser |
|
public void anonymous() throws Exception { |
|
// override default to run as anonymous user |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@ExtendWith(SpringExtension.class) |
|
@WithMockUser |
|
class WithUserClassLevelAuthenticationTests { |
|
@Test |
|
fun withMockUser1() { |
|
} |
|
|
|
@Test |
|
fun withMockUser2() { |
|
} |
|
|
|
@Test |
|
@WithAnonymousUser |
|
fun anonymous() { |
|
// override default to run as anonymous user |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. |
|
This is the equivalent of happening before JUnit's `@Before`. |
|
You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. |
|
|
|
[source,java] |
|
---- |
|
@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) |
|
---- |
|
|
|
|
|
[[test-method-withuserdetails]] |
|
== @WithUserDetails |
|
|
|
While `@WithMockUser` is a very convenient way to get started, it may not work in all instances. |
|
For example, it is common for applications to expect that the `Authentication` principal be of a specific type. |
|
This is done so that the application can refer to the principal as the custom type and reduce coupling on Spring Security. |
|
|
|
The custom principal is often times returned by a custom `UserDetailsService` that returns an object that implements both `UserDetails` and the custom type. |
|
For situations like this, it is useful to create the test user using the custom `UserDetailsService`. |
|
That is exactly what `@WithUserDetails` does. |
|
|
|
Assuming we have a `UserDetailsService` exposed as a bean, the following test will be invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of "user". |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test |
|
@WithUserDetails |
|
public void getMessageWithUserDetails() { |
|
String message = messageService.getMessage(); |
|
... |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test |
|
@WithUserDetails |
|
fun getMessageWithUserDetails() { |
|
val message: String = messageService.getMessage() |
|
// ... |
|
} |
|
---- |
|
====== |
|
|
|
We can also customize the username used to lookup the user from our `UserDetailsService`. |
|
For example, this test would be run with a principal that is returned from the `UserDetailsService` with the username of "customUsername". |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test |
|
@WithUserDetails("customUsername") |
|
public void getMessageWithUserDetailsCustomUsername() { |
|
String message = messageService.getMessage(); |
|
... |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test |
|
@WithUserDetails("customUsername") |
|
fun getMessageWithUserDetailsCustomUsername() { |
|
val message: String = messageService.getMessage() |
|
// ... |
|
} |
|
---- |
|
====== |
|
|
|
We can also provide an explicit bean name to look up the `UserDetailsService`. |
|
For example, this test would look up the username of "customUsername" using the `UserDetailsService` with the bean name "myUserDetailsService". |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Test |
|
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService") |
|
public void getMessageWithUserDetailsServiceBeanName() { |
|
String message = messageService.getMessage(); |
|
... |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Test |
|
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService") |
|
fun getMessageWithUserDetailsServiceBeanName() { |
|
val message: String = messageService.getMessage() |
|
// ... |
|
} |
|
---- |
|
====== |
|
|
|
Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user. |
|
However unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist. |
|
|
|
By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. |
|
This is the equivalent of happening before JUnit's `@Before`. |
|
You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. |
|
|
|
[source,java] |
|
---- |
|
@WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION) |
|
---- |
|
|
|
|
|
[[test-method-withsecuritycontext]] |
|
== @WithSecurityContext |
|
|
|
We have seen that `@WithMockUser` is an excellent choice if we are not using a custom `Authentication` principal. |
|
Next we discovered that `@WithUserDetails` would allow us to use a custom `UserDetailsService` to create our `Authentication` principal but required the user to exist. |
|
We will now see an option that allows the most flexibility. |
|
|
|
We can create our own annotation that uses the `@WithSecurityContext` to create any `SecurityContext` we want. |
|
For example, we might create an annotation named `@WithMockCustomUser` as shown below: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Retention(RetentionPolicy.RUNTIME) |
|
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) |
|
public @interface WithMockCustomUser { |
|
|
|
String username() default "rob"; |
|
|
|
String name() default "Rob Winch"; |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Retention(AnnotationRetention.RUNTIME) |
|
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class) |
|
annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch") |
|
---- |
|
====== |
|
|
|
You can see that `@WithMockCustomUser` is annotated with the `@WithSecurityContext` annotation. |
|
This is what signals to Spring Security Test support that we intend to create a `SecurityContext` for the test. |
|
The `@WithSecurityContext` annotation requires we specify a `SecurityContextFactory` that will create a new `SecurityContext` given our `@WithMockCustomUser` annotation. |
|
You can find our `WithMockCustomUserSecurityContextFactory` implementation below: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
public class WithMockCustomUserSecurityContextFactory |
|
implements WithSecurityContextFactory<WithMockCustomUser> { |
|
@Override |
|
public SecurityContext createSecurityContext(WithMockCustomUser customUser) { |
|
SecurityContext context = SecurityContextHolder.createEmptyContext(); |
|
|
|
CustomUserDetails principal = |
|
new CustomUserDetails(customUser.name(), customUser.username()); |
|
Authentication auth = |
|
UsernamePasswordAuthenticationToken.authenticated(principal, "password", principal.getAuthorities()); |
|
context.setAuthentication(auth); |
|
return context; |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory<WithMockCustomUser> { |
|
override fun createSecurityContext(customUser: WithMockCustomUser): SecurityContext { |
|
val context = SecurityContextHolder.createEmptyContext() |
|
val principal = CustomUserDetails(customUser.name, customUser.username) |
|
val auth: Authentication = |
|
UsernamePasswordAuthenticationToken(principal, "password", principal.authorities) |
|
context.authentication = auth |
|
return context |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` will ensure that our `SecurityContext` is populated appropriately. |
|
|
|
When creating your own `WithSecurityContextFactory` implementations, it is nice to know that they can be annotated with standard Spring annotations. |
|
For example, the `WithUserDetailsSecurityContextFactory` uses the `@Autowired` annotation to acquire the `UserDetailsService`: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
final class WithUserDetailsSecurityContextFactory |
|
implements WithSecurityContextFactory<WithUserDetails> { |
|
|
|
private UserDetailsService userDetailsService; |
|
|
|
@Autowired |
|
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) { |
|
this.userDetailsService = userDetailsService; |
|
} |
|
|
|
public SecurityContext createSecurityContext(WithUserDetails withUser) { |
|
String username = withUser.value(); |
|
Assert.hasLength(username, "value() must be non-empty String"); |
|
UserDetails principal = userDetailsService.loadUserByUsername(username); |
|
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities()); |
|
SecurityContext context = SecurityContextHolder.createEmptyContext(); |
|
context.setAuthentication(authentication); |
|
return context; |
|
} |
|
} |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
class WithUserDetailsSecurityContextFactory @Autowired constructor(private val userDetailsService: UserDetailsService) : |
|
WithSecurityContextFactory<WithUserDetails> { |
|
override fun createSecurityContext(withUser: WithUserDetails): SecurityContext { |
|
val username: String = withUser.value |
|
Assert.hasLength(username, "value() must be non-empty String") |
|
val principal = userDetailsService.loadUserByUsername(username) |
|
val authentication: Authentication = |
|
UsernamePasswordAuthenticationToken(principal, principal.password, principal.authorities) |
|
val context = SecurityContextHolder.createEmptyContext() |
|
context.authentication = authentication |
|
return context |
|
} |
|
} |
|
---- |
|
====== |
|
|
|
By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. |
|
This is the equivalent of happening before JUnit's `@Before`. |
|
You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. |
|
|
|
[source,java] |
|
---- |
|
@WithSecurityContext(setupBefore = TestExecutionEvent.TEST_EXECUTION) |
|
---- |
|
|
|
|
|
[[test-method-meta-annotations]] |
|
== Test Meta Annotations |
|
|
|
If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes. |
|
For example, if there are many tests related to an administrative user with the username "admin" and the roles `ROLE_USER` and `ROLE_ADMIN` you would have to write: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@WithMockUser(username="admin",roles={"USER","ADMIN"}) |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@WithMockUser(username="admin",roles=["USER","ADMIN"]) |
|
---- |
|
====== |
|
|
|
Rather than repeating this everywhere, we can use a meta annotation. |
|
For example, we could create a meta annotation named `WithMockAdmin`: |
|
|
|
[tabs] |
|
====== |
|
Java:: |
|
+ |
|
[source,java,role="primary"] |
|
---- |
|
@Retention(RetentionPolicy.RUNTIME) |
|
@WithMockUser(value="rob",roles="ADMIN") |
|
public @interface WithMockAdmin { } |
|
---- |
|
|
|
Kotlin:: |
|
+ |
|
[source,kotlin,role="secondary"] |
|
---- |
|
@Retention(AnnotationRetention.RUNTIME) |
|
@WithMockUser(value = "rob", roles = ["ADMIN"]) |
|
annotation class WithMockAdmin |
|
---- |
|
====== |
|
|
|
Now we can use `@WithMockAdmin` in the same way as the more verbose `@WithMockUser`. |
|
|
|
Meta annotations work with any of the testing annotations described above. |
|
For example, this means we could create a meta annotation for `@WithUserDetails("admin")` as well.
|
|
|