Allow WithSecurityContextTestExecutionListener to execute after @Before

Fixes: gh-2935
This commit is contained in:
Rob Winch 2018-03-07 17:05:32 -06:00
parent 055a2ca917
commit abae2f3e87
12 changed files with 465 additions and 38 deletions

View File

@ -144,6 +144,15 @@ For example, the following would run every test with a user with the username "a
public class WithMockUserTests { public class WithMockUserTests {
---- ----
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]] [[test-method-withanonymoususer]]
=== @WithAnonymousUser === @WithAnonymousUser
@ -174,6 +183,15 @@ public class WithUserClassLevelAuthenticationTests {
} }
---- ----
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]] [[test-method-withuserdetails]]
=== @WithUserDetails === @WithUserDetails
@ -227,6 +245,16 @@ public void getMessageWithUserDetailsServiceBeanName() {
Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user. 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. 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]] [[test-method-withsecuritycontext]]
=== @WithSecurityContext === @WithSecurityContext
@ -301,6 +329,16 @@ final class WithUserDetailsSecurityContextFactory
} }
---- ----
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-method-meta-annotations]]
=== Test Meta Annotations === Test Meta Annotations

View File

@ -400,28 +400,16 @@ git clone https://github.com/spring-projects/spring-security.git
This will give you access to the entire project history (including all releases and branches) on your local machine. This will give you access to the entire project history (including all releases and branches) on your local machine.
[[new]] [[new]]
== What's New in Spring Security 5.0 == What's New in Spring Security 5.1
Spring Security 5.0 provides a number of new features as well as support for Spring Framework 5. Spring Security 5.1 provides a number of new features.
In total there were 400+ enhancements and bugs resolved. Below are the highlights of the release.
You can find the change log at
https://github.com/spring-projects/spring-security/milestone/90?closed=1[5.0.0.M1]
https://github.com/spring-projects/spring-security/milestone/97?closed=1[5.0.0.M2]
https://github.com/spring-projects/spring-security/milestone/100?closed=1[5.0.0.M3]
https://github.com/spring-projects/spring-security/milestone/101?closed=1[5.0.0.M4]
https://github.com/spring-projects/spring-security/milestone/102?closed=1[5.0.0.M5]
https://github.com/spring-projects/spring-security/milestone/103?closed=1[5.0.0.RC1]
https://github.com/spring-projects/spring-security/milestone/98?closed=1[5.0.0.RELEASE].
Below are the highlights of this milestone release.
=== New Features === New Features
* <<jc-oauth2login,OAuth 2.0 Login>> * <<test-method>> - Support for customizing when the `SecurityContext` is setup in the test.
* Reactive Support For example, `@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)` will setup a user after JUnit's `@Before` and before the test executes.
** <<jc-webflux,@EnableWebFluxSecurity>>
** <<jc-erms,@EnableReactiveMethodSecurity>>
** <<test-webflux,WebFlux Testing Support>>
* Modernized <<core-services-password-encoding,Password Encoding>>
[[samples]] [[samples]]
== Samples and Guides (Start Here) == Samples and Guides (Start Here)

View File

@ -0,0 +1,38 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.test.context.support;
import org.springframework.test.context.TestContext;
/**
* Represents the events on the methods of {@link org.springframework.test.context.TestExecutionListener}
*
* @author Rob Winch
* @since 5.1
*/
public enum TestExecutionEvent {
/**
* Associated to {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
* event.
*/
TEST_METHOD,
/**
* Associated to {@link org.springframework.test.context.TestExecutionListener#beforeTestExecution(TestContext)}
* event.
*/
TEST_EXECUTION
}

View File

@ -22,8 +22,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.test.context.TestContext;
/** /**
* When used with {@link WithSecurityContextTestExecutionListener} this * When used with {@link WithSecurityContextTestExecutionListener} this
@ -58,4 +60,13 @@ import org.springframework.security.core.context.SecurityContext;
@WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class) @WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class)
public @interface WithAnonymousUser { public @interface WithAnonymousUser {
/**
* Determines when the {@link SecurityContext} is setup. The default is before
* {@link TestExecutionEvent#TEST_METHOD} which occurs during
* {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
* @return the {@link TestExecutionEvent} to initialize before
* @since 5.1
*/
@AliasFor(annotation = WithSecurityContext.class)
TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
} }

View File

@ -22,10 +22,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.context.TestContext;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
/** /**
@ -102,4 +104,14 @@ public @interface WithMockUser {
* @return * @return
*/ */
String password() default "password"; String password() default "password";
}
/**
* Determines when the {@link SecurityContext} is setup. The default is before
* {@link TestExecutionEvent#TEST_METHOD} which occurs during
* {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
* @return the {@link TestExecutionEvent} to initialize before
* @since 5.1
*/
@AliasFor(annotation = WithSecurityContext.class)
TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
}

View File

@ -25,6 +25,7 @@ import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.test.context.TestContext;
/** /**
* <p> * <p>
@ -61,4 +62,14 @@ public @interface WithSecurityContext {
* @return * @return
*/ */
Class<? extends WithSecurityContextFactory<? extends Annotation>> factory(); Class<? extends WithSecurityContextFactory<? extends Annotation>> factory();
}
/**
* Determines when the {@link SecurityContext} is setup. The default is before
* {@link TestExecutionEvent#TEST_METHOD} which occurs during
* {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
* @return the {@link TestExecutionEvent} to initialize before
* @since 5.1
*/
TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
}

View File

@ -20,6 +20,7 @@ import java.lang.reflect.AnnotatedElement;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -46,6 +47,8 @@ import org.springframework.test.web.servlet.MockMvc;
public class WithSecurityContextTestExecutionListener public class WithSecurityContextTestExecutionListener
extends AbstractTestExecutionListener { extends AbstractTestExecutionListener {
static final String SECURITY_CONTEXT_ATTR_NAME = WithSecurityContextTestExecutionListener.class.getName().concat(".SECURITY_CONTEXT");
/** /**
* Sets up the {@link SecurityContext} for each test method. First the specific method * Sets up the {@link SecurityContext} for each test method. First the specific method
* is inspected for a {@link WithSecurityContext} or {@link Annotation} that has * is inspected for a {@link WithSecurityContext} or {@link Annotation} that has
@ -54,46 +57,68 @@ public class WithSecurityContextTestExecutionListener
*/ */
@Override @Override
public void beforeTestMethod(TestContext testContext) throws Exception { public void beforeTestMethod(TestContext testContext) throws Exception {
SecurityContext securityContext = createSecurityContext( TestSecurityContext testSecurityContext = createTestSecurityContext(
testContext.getTestMethod(), testContext); testContext.getTestMethod(), testContext);
if (securityContext == null) { if (testSecurityContext == null) {
securityContext = createSecurityContext(testContext.getTestClass(), testSecurityContext = createTestSecurityContext(testContext.getTestClass(),
testContext); testContext);
} }
if (securityContext != null) { if (testSecurityContext == null) {
return;
}
SecurityContext securityContext = testSecurityContext.securityContext;
if (testSecurityContext.getTestExecutionEvent() == TestExecutionEvent.TEST_METHOD) {
TestSecurityContextHolder.setContext(securityContext);
} else {
testContext.setAttribute(SECURITY_CONTEXT_ATTR_NAME, securityContext);
}
}
/**
* If configured before test execution sets the SecurityContext
* @since 5.1
*/
@Override
public void beforeTestExecution(TestContext testContext) {
SecurityContext securityContext = (SecurityContext) testContext.removeAttribute(SECURITY_CONTEXT_ATTR_NAME);
if(securityContext != null) {
TestSecurityContextHolder.setContext(securityContext); TestSecurityContextHolder.setContext(securityContext);
} }
} }
private SecurityContext createSecurityContext(AnnotatedElement annotated, private TestSecurityContext createTestSecurityContext(AnnotatedElement annotated,
TestContext context) { TestContext context) {
WithSecurityContext withSecurityContext = AnnotationUtils WithSecurityContext withSecurityContext = AnnotatedElementUtils
.findAnnotation(annotated, WithSecurityContext.class); .findMergedAnnotation(annotated, WithSecurityContext.class);
return createSecurityContext(annotated, withSecurityContext, context); return createTestSecurityContext(annotated, withSecurityContext, context);
} }
private SecurityContext createSecurityContext(Class<?> annotated, private TestSecurityContext createTestSecurityContext(Class<?> annotated,
TestContext context) { TestContext context) {
MetaAnnotationUtils.AnnotationDescriptor<WithSecurityContext> withSecurityContextDescriptor = MetaAnnotationUtils MetaAnnotationUtils.AnnotationDescriptor<WithSecurityContext> withSecurityContextDescriptor = MetaAnnotationUtils
.findAnnotationDescriptor(annotated, WithSecurityContext.class); .findAnnotationDescriptor(annotated, WithSecurityContext.class);
WithSecurityContext withSecurityContext = withSecurityContextDescriptor == null WithSecurityContext withSecurityContext = withSecurityContextDescriptor == null
? null : withSecurityContextDescriptor.getAnnotation(); ? null : withSecurityContextDescriptor.getAnnotation();
return createSecurityContext(annotated, withSecurityContext, context); return createTestSecurityContext(annotated, withSecurityContext, context);
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private SecurityContext createSecurityContext(AnnotatedElement annotated, private TestSecurityContext createTestSecurityContext(AnnotatedElement annotated,
WithSecurityContext withSecurityContext, TestContext context) { WithSecurityContext withSecurityContext, TestContext context) {
if (withSecurityContext == null) { if (withSecurityContext == null) {
return null; return null;
} }
withSecurityContext = AnnotationUtils
.synthesizeAnnotation(withSecurityContext, annotated);
WithSecurityContextFactory factory = createFactory(withSecurityContext, context); WithSecurityContextFactory factory = createFactory(withSecurityContext, context);
Class<? extends Annotation> type = (Class<? extends Annotation>) GenericTypeResolver Class<? extends Annotation> type = (Class<? extends Annotation>) GenericTypeResolver
.resolveTypeArgument(factory.getClass(), .resolveTypeArgument(factory.getClass(),
WithSecurityContextFactory.class); WithSecurityContextFactory.class);
Annotation annotation = findAnnotation(annotated, type); Annotation annotation = findAnnotation(annotated, type);
TestExecutionEvent initialize = withSecurityContext.setupBefore();
try { try {
return factory.createSecurityContext(annotation); return new TestSecurityContext(factory.createSecurityContext(annotation), initialize);
} }
catch (RuntimeException e) { catch (RuntimeException e) {
throw new IllegalStateException( throw new IllegalStateException(
@ -150,4 +175,22 @@ public class WithSecurityContextTestExecutionListener
public int getOrder() { public int getOrder() {
return 10000; return 10000;
} }
static class TestSecurityContext {
private final SecurityContext securityContext;
private final TestExecutionEvent testExecutionEvent;
TestSecurityContext(SecurityContext securityContext, TestExecutionEvent testExecutionEvent) {
this.securityContext = securityContext;
this.testExecutionEvent = testExecutionEvent;
}
public SecurityContext getSecurityContext() {
return this.securityContext;
}
public TestExecutionEvent getTestExecutionEvent() {
return this.testExecutionEvent;
}
}
} }

View File

@ -22,11 +22,13 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.test.context.TestContext;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
/** /**
@ -69,4 +71,14 @@ public @interface WithUserDetails {
* @since 4.1 * @since 4.1
*/ */
String userDetailsServiceBeanName() default ""; String userDetailsServiceBeanName() default "";
}
/**
* Determines when the {@link SecurityContext} is setup. The default is before
* {@link TestExecutionEvent#TEST_METHOD} which occurs during
* {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)}
* @return the {@link TestExecutionEvent} to initialize before
* @since 5.1
*/
@AliasFor(annotation = WithSecurityContext.class)
TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.test.context.support;
import org.junit.Test;
import org.springframework.core.annotation.AnnotatedElementUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
* @since 5.0
*/
public class WithAnonymousUserTests {
@Test
public void defaults() {
WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(Annotated.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
}
@WithAnonymousUser
private class Annotated {
}
@Test
public void findMergedAnnotationWhenSetupExplicitThenOverridden() {
WithSecurityContext context = AnnotatedElementUtils
.findMergedAnnotation(SetupExplicit.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
}
@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_METHOD)
private class SetupExplicit {
}
@Test
public void findMergedAnnotationWhenSetupOverriddenThenOverridden() {
WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(SetupOverridden.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION);
}
@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
private class SetupOverridden {
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,26 +13,58 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.security.test.context.support; package org.springframework.security.test.context.support;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
public class WithMockUserTests { public class WithMockUserTests {
@Test @Test
public void defaults() { public void defaults() {
WithMockUser mockUser = AnnotationUtils.findAnnotation(Annotated.class, WithMockUser mockUser = AnnotatedElementUtils.findMergedAnnotation(Annotated.class,
WithMockUser.class); WithMockUser.class);
assertThat(mockUser.value()).isEqualTo("user"); assertThat(mockUser.value()).isEqualTo("user");
assertThat(mockUser.username()).isEmpty(); assertThat(mockUser.username()).isEmpty();
assertThat(mockUser.password()).isEqualTo("password"); assertThat(mockUser.password()).isEqualTo("password");
assertThat(mockUser.roles()).containsOnly("USER"); assertThat(mockUser.roles()).containsOnly("USER");
assertThat(mockUser.setupBefore()).isEqualByComparingTo(TestExecutionEvent.TEST_METHOD);
WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(Annotated.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
} }
@WithMockUser @WithMockUser
private class Annotated { private class Annotated {
} }
}
@Test
public void findMergedAnnotationWhenSetupExplicitThenOverridden() {
WithSecurityContext context = AnnotatedElementUtils
.findMergedAnnotation(SetupExplicit.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
}
@WithMockUser(setupBefore = TestExecutionEvent.TEST_METHOD)
private class SetupExplicit {
}
@Test
public void findMergedAnnotationWhenSetupOverriddenThenOverridden() {
WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(SetupOverridden.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION);
}
@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
private class SetupOverridden {
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.test.context.support;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Rob Winch
* @since 5.0
*/
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(classes = WithSecurityContextTestExecutionListenerTests.NoOpConfiguration.class)
public class WithSecurityContextTestExecutionListenerTests {
@ClassRule
public static final SpringClassRule spring = new SpringClassRule();
@Rule
public final SpringMethodRule springMethod = new SpringMethodRule();
@Autowired
private ApplicationContext applicationContext;
@Mock
private TestContext testContext;
private WithSecurityContextTestExecutionListener listener = new WithSecurityContextTestExecutionListener();
@After
public void cleanup() {
TestSecurityContextHolder.clearContext();
}
@Test
public void beforeTestMethodWhenWithMockUserTestExecutionDefaultThenSecurityContextSet() throws Exception {
Method testMethod = TheTest.class.getMethod("withMockUserDefault");
when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext);
when(this.testContext.getTestMethod()).thenReturn(testMethod);
this.listener.beforeTestMethod(this.testContext);
assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNotNull();
verify(this.testContext, never()).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class));
}
@Test
public void beforeTestMethodWhenWithMockUserTestMethodThenSecurityContextSet() throws Exception {
Method testMethod = TheTest.class.getMethod("withMockUserTestMethod");
when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext);
when(this.testContext.getTestMethod()).thenReturn(testMethod);
this.listener.beforeTestMethod(this.testContext);
assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNotNull();
verify(this.testContext, never()).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class));
}
@Test
public void beforeTestMethodWhenWithMockUserTestExecutionThenTestContextSet() throws Exception {
Method testMethod = TheTest.class.getMethod("withMockUserTestExecution");
when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext);
when(this.testContext.getTestMethod()).thenReturn(testMethod);
this.listener.beforeTestMethod(this.testContext);
assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNull();
verify(this.testContext).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class));
}
@Test
public void beforeTestExecutionWhenTestContextNullThenSecurityContextNotSet() throws Exception {
this.listener.beforeTestExecution(this.testContext);
assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNull();
}
@Test
public void beforeTestExecutionWhenTestContextNotNullThenSecurityContextSet() throws Exception {
SecurityContextImpl securityContext = new SecurityContextImpl();
securityContext.setAuthentication(new TestingAuthenticationToken("user", "passsword", "ROLE_USER"));
when(this.testContext.removeAttribute(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME)).thenReturn(securityContext);
this.listener.beforeTestExecution(this.testContext);
assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isEqualTo(securityContext.getAuthentication());
}
@Configuration
static class NoOpConfiguration {}
static class TheTest {
@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
public void withMockUserTestExecution() {
}
@WithMockUser(setupBefore = TestExecutionEvent.TEST_METHOD)
public void withMockUserTestMethod() {
}
@WithMockUser
public void withMockUserDefault() {
}
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package org.springframework.security.test.context.support;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
public class WithUserDetailsTests { public class WithUserDetailsTests {
@ -27,9 +28,41 @@ public class WithUserDetailsTests {
WithUserDetails userDetails = AnnotationUtils.findAnnotation(Annotated.class, WithUserDetails userDetails = AnnotationUtils.findAnnotation(Annotated.class,
WithUserDetails.class); WithUserDetails.class);
assertThat(userDetails.value()).isEqualTo("user"); assertThat(userDetails.value()).isEqualTo("user");
WithSecurityContext context = AnnotatedElementUtils
.findMergedAnnotation(Annotated.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
} }
@WithUserDetails @WithUserDetails
private static class Annotated { private static class Annotated {
} }
@Test
public void findMergedAnnotationWhenSetupExplicitThenOverridden() {
WithSecurityContext context = AnnotatedElementUtils
.findMergedAnnotation(SetupExplicit.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD);
}
@WithUserDetails(setupBefore = TestExecutionEvent.TEST_METHOD)
private class SetupExplicit {
}
@Test
public void findMergedAnnotationWhenSetupOverriddenThenOverridden() {
WithSecurityContext context = AnnotatedElementUtils
.findMergedAnnotation(SetupOverridden.class,
WithSecurityContext.class);
assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION);
}
@WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)
private class SetupOverridden {
}
} }