Make Mock/Spy qualifiers part of context cache key
Refine @MockBean/@SpyBean qualifier support so that qualifiers form part of the context cache key. Prior to this commit is was possible that two different tests could accidentally share the same context if they defined the same @Mock but with different @Qualifiers. See gh-6753pull/6919/merge
parent
04448d6bd9
commit
aad40093ff
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.boot.test.mock.mockito;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
|
||||
/**
|
||||
* Definition of a Spring {@link Qualifier @Qualifier}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @see Definition
|
||||
*/
|
||||
class QualifierDefinition {
|
||||
|
||||
private final Field field;
|
||||
|
||||
private final DependencyDescriptor descriptor;
|
||||
|
||||
private final Set<Annotation> annotations;
|
||||
|
||||
QualifierDefinition(Field field, Set<Annotation> annotations) {
|
||||
// We can't use the field or descriptor as part of the context key
|
||||
// but we can assume that if two fields have the same qualifiers then
|
||||
// it's safe for Spring to use either for qualifier logic
|
||||
this.field = field;
|
||||
this.descriptor = new DependencyDescriptor(field, true);
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
public boolean matches(ConfigurableListableBeanFactory beanFactory, String beanName) {
|
||||
return beanFactory.isAutowireCandidate(beanName, this.descriptor);
|
||||
}
|
||||
|
||||
public void applyTo(RootBeanDefinition definition) {
|
||||
definition.setQualifiedElement(this.field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.annotations.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
QualifierDefinition other = (QualifierDefinition) obj;
|
||||
return this.annotations.equals(other.annotations);
|
||||
}
|
||||
|
||||
public static QualifierDefinition forElement(AnnotatedElement element) {
|
||||
if (element != null && element instanceof Field) {
|
||||
Field field = (Field) element;
|
||||
Set<Annotation> annotations = getQualifierAnnotations(field);
|
||||
if (!annotations.isEmpty()) {
|
||||
return new QualifierDefinition(field, annotations);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Set<Annotation> getQualifierAnnotations(Field field) {
|
||||
// Assume that any annotations other than @MockBean/@SpyBean are qualifiers
|
||||
Annotation[] candidates = field.getDeclaredAnnotations();
|
||||
Set<Annotation> annotations = new HashSet<Annotation>(candidates.length);
|
||||
for (Annotation candidate : candidates) {
|
||||
if (!isMockOrSpyAnnotation(candidate)) {
|
||||
annotations.add(candidate);
|
||||
}
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private static boolean isMockOrSpyAnnotation(Annotation candidate) {
|
||||
Class<? extends Annotation> type = candidate.annotationType();
|
||||
return (type.equals(MockBean.class) || type.equals(SpyBean.class)
|
||||
|| AnnotationUtils.isAnnotationMetaPresent(type, MockBean.class)
|
||||
|| AnnotationUtils.isAnnotationMetaPresent(type, SpyBean.class));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.boot.test.mock.mockito;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link QualifierDefinition}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class QualifierDefinitionTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<DependencyDescriptor> descriptorCaptor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forElementFieldIsNullShouldReturnNull() throws Exception {
|
||||
assertThat(QualifierDefinition.forElement((Field) null)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forElementWhenElementIsNotFieldShouldReturnNull() throws Exception {
|
||||
assertThat(QualifierDefinition.forElement(getClass())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forElementWhenElementIsFieldWithNoQualifiersShouldReturnNull()
|
||||
throws Exception {
|
||||
QualifierDefinition definition = QualifierDefinition
|
||||
.forElement(ReflectionUtils.findField(ConfigA.class, "noQualifier"));
|
||||
assertThat(definition).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forElementWhenElementIsFieldWithQualifierShouldReturnDefinition()
|
||||
throws Exception {
|
||||
QualifierDefinition definition = QualifierDefinition
|
||||
.forElement(ReflectionUtils.findField(ConfigA.class, "directQualifier"));
|
||||
assertThat(definition).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesShouldCallBeanFactory() throws Exception {
|
||||
Field field = ReflectionUtils.findField(ConfigA.class, "directQualifier");
|
||||
QualifierDefinition qualifierDefinition = QualifierDefinition.forElement(field);
|
||||
qualifierDefinition.matches(this.beanFactory, "bean");
|
||||
verify(this.beanFactory).isAutowireCandidate(eq("bean"),
|
||||
this.descriptorCaptor.capture());
|
||||
assertThat(this.descriptorCaptor.getValue().getAnnotatedElement())
|
||||
.isEqualTo(field);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyToShouldSetQualifierElement() throws Exception {
|
||||
Field field = ReflectionUtils.findField(ConfigA.class, "directQualifier");
|
||||
QualifierDefinition qualifierDefinition = QualifierDefinition.forElement(field);
|
||||
RootBeanDefinition definition = new RootBeanDefinition();
|
||||
qualifierDefinition.applyTo(definition);
|
||||
assertThat(definition.getQualifiedElement()).isEqualTo(field);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashCodeAndEqualsShouldWorkOnDifferentClasses() throws Exception {
|
||||
QualifierDefinition directQualifier1 = QualifierDefinition
|
||||
.forElement(ReflectionUtils.findField(ConfigA.class, "directQualifier"));
|
||||
QualifierDefinition directQualifier2 = QualifierDefinition
|
||||
.forElement(ReflectionUtils.findField(ConfigB.class, "directQualifier"));
|
||||
QualifierDefinition differentDirectQualifier1 = QualifierDefinition.forElement(
|
||||
ReflectionUtils.findField(ConfigA.class, "differentDirectQualifier"));
|
||||
QualifierDefinition differentDirectQualifier2 = QualifierDefinition.forElement(
|
||||
ReflectionUtils.findField(ConfigB.class, "differentDirectQualifier"));
|
||||
QualifierDefinition customQualifier1 = QualifierDefinition
|
||||
.forElement(ReflectionUtils.findField(ConfigA.class, "customQualifier"));
|
||||
QualifierDefinition customQualifier2 = QualifierDefinition
|
||||
.forElement(ReflectionUtils.findField(ConfigB.class, "customQualifier"));
|
||||
assertThat(directQualifier1.hashCode()).isEqualTo(directQualifier2.hashCode());
|
||||
assertThat(differentDirectQualifier1.hashCode())
|
||||
.isEqualTo(differentDirectQualifier2.hashCode());
|
||||
assertThat(customQualifier1.hashCode()).isEqualTo(customQualifier2.hashCode());
|
||||
assertThat(differentDirectQualifier1).isEqualTo(differentDirectQualifier1)
|
||||
.isEqualTo(differentDirectQualifier2).isNotEqualTo(directQualifier2);
|
||||
assertThat(directQualifier1).isEqualTo(directQualifier1)
|
||||
.isEqualTo(directQualifier2).isNotEqualTo(differentDirectQualifier1);
|
||||
assertThat(customQualifier1).isEqualTo(customQualifier1)
|
||||
.isEqualTo(customQualifier2).isNotEqualTo(differentDirectQualifier1);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ConfigA {
|
||||
|
||||
@MockBean
|
||||
private Object noQualifier;
|
||||
|
||||
@MockBean
|
||||
@Qualifier("test")
|
||||
private Object directQualifier;
|
||||
|
||||
@MockBean
|
||||
@Qualifier("different")
|
||||
private Object differentDirectQualifier;
|
||||
|
||||
@MockBean
|
||||
@CustomQualifier
|
||||
private Object customQualifier;
|
||||
|
||||
}
|
||||
|
||||
static class ConfigB {
|
||||
|
||||
@MockBean
|
||||
@Qualifier("test")
|
||||
private Object directQualifier;
|
||||
|
||||
@MockBean
|
||||
@Qualifier("different")
|
||||
private Object differentDirectQualifier;
|
||||
|
||||
@MockBean
|
||||
@CustomQualifier
|
||||
private Object customQualifier;
|
||||
|
||||
}
|
||||
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CustomQualifier {
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue