) testContext.getAttribute(STUBBED_FIELDS);
+ if (!CollectionUtils.isEmpty(beanStubbedFields)) {
+ beanStubbedFields.forEach(BeanInjectorStub::reset);
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/MockBeanInjector.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/MockBeanInjector.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc020f311b270d9f2e7eed4da1afe00bef59ee9c
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/MockBeanInjector.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.annotation;
+
+import org.mockito.Answers;
+import org.mockito.MockSettings;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that can be used to create mocks and inject mock to a target bean's field.
+ *
+ * Injector target bean can be found by type or by {@link #name() bean name}. When registered by
+ * type, any existing single bean of a matching type (including subclasses) in the context
+ * will be found for injector. If no suitable bean could be found, {@link IllegalStateException} will be thrown.
+ *
+ * Field in target bean will be found by {@link #field()}. If no field could be found, {@link IllegalStateException} will be thrown.
+ *
+ *
+ * Typical usage might be:
+ * @RunWith(SpringRunner.class)
+ * public class ExampleServiceTest {
+ *
+ * @Autowired
+ * private ExampleService service;
+ *
+ * @MockBeanInjector(type = ExampleService.class, field = "fieldA")
+ * private FieldAClass mock;
+ *
+ * @Test
+ * public void testInjectExampleServiceFieldA() {
+ * // 1. mock external dependency
+ * given(mock.callSomeMethod(...))
+ * .willReturn(...);
+ *
+ * // 2. perform testing
+ * service.doSomething();
+ *
+ * // 3. behavioral-driven testing / standard unit-testing
+ * then(mock)
+ * .should(atLeastOnce())
+ * .callSomeMethod(...);
+ *
+ * assertThat(...)...;
+ * }
+ *
+ * #064;Configuration
+ * @Import(ExampleService.class) // A @Component injected with ExampleService
+ * static class Config {
+ * }
+ * }
+ *
+ * If there is more than one bean of the requested type, qualifier metadata must be
+ * specified at field level:
+ * @RunWith(SpringRunner.class)
+ * public class ExampleTests {
+ *
+ * @MockBeanInjector(type = ExampleService.class, field = "fieldA")
+ * @Qualifier("example")
+ * private ExampleService service;
+ *
+ * ...
+ * }
+ *
+ * @author pengym
+ * @version MockBeanInjector.java, v 0.1 2023年08月07日 15:32 pengym
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface MockBeanInjector {
+
+ /**
+ * The name for field which should inject the mock.
+ * When can not find the target field, an {@link IllegalStateException} will be thrown.
+ */
+ String field();
+
+ /**
+ * The name of the bean to inject the mock to a field.
+ * @return the name of the target bean
+ */
+ String name() default "";
+
+ /**
+ * The class type of the bean to inject the mock to a field. This is an alias of {@link #type()} which can be used for
+ * brevity if no other attributes are defined. See {@link #type()} for details.
+ * @return the class ype of the target bean
+ */
+ @AliasFor("type")
+ Class> value() default void.class;
+
+ /**
+ * The class type of the bean to inject the mock to a field
+ * @return the class ype of the target bean
+ */
+ @AliasFor("value")
+ Class> type() default void.class;
+
+ /**
+ * The application context id to find the target bean. If not specified, the root application context will be used.
+ *
When can not find the target SOFA module for the specified module name, an {@link IllegalStateException} will be thrown.
+ */
+ String module() default "";
+
+ /**
+ * Any extra interfaces that should also be declared on the mock. See
+ * {@link MockSettings#extraInterfaces(Class...)} for details.
+ * @return any extra interfaces
+ */
+ Class>[] extraInterfaces() default {};
+
+ /**
+ * The {@link Answers} type to use on the mock.
+ * @return the answer type
+ */
+ Answers answer() default Answers.RETURNS_DEFAULTS;
+
+ /**
+ * If the generated mock is serializable. See {@link MockSettings#serializable()} for
+ * details.
+ * @return if the mock is serializable
+ */
+ boolean serializable() default false;
+
+ /**
+ * The reset mode to apply to the mock. The default is {@link MockReset#AFTER}
+ * meaning that mocks are automatically reset after each test method is invoked.
+ * @return the reset mode
+ */
+ MockReset reset() default MockReset.AFTER;
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/SpyBeanInjector.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/SpyBeanInjector.java
new file mode 100644
index 0000000000000000000000000000000000000000..94b3e76fa75190a29e1c4a4e3331dbd519c9f527
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/annotation/SpyBeanInjector.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.annotation;
+
+import org.mockito.Mockito;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that can be used to create spies and inject spy to a target bean's field.
+ *
+ * Injector target bean can be found by type or by {@link #name() bean name}. When registered by
+ * type, any existing single bean of a matching type (including subclasses) in the context
+ * will be found for injector. If no suitable bean could be found, {@link IllegalStateException} will be thrown.
+ *
+ * Field in target bean will be found by {@link #field()}. If no field could be found, {@link IllegalStateException} will be thrown.
+ *
+ *
+ * Typical usage might be:
+ * @RunWith(SpringRunner.class)
+ * public class ExampleServiceTest {
+ *
+ * @Autowired
+ * private ExampleService service;
+ *
+ * @SpyBeanInjector(type = ExampleService.class, field = "fieldA")
+ * private FieldAClass spy;
+ *
+ * @Test
+ * public void testInjectExampleServiceFieldA() {
+ * // 1. spy external dependency
+ * given(spy.callSomeMethod(...))
+ * .willReturn(...);
+ *
+ * // 2. perform testing
+ * service.doSomething();
+ *
+ * // 3. behavioral-driven testing / standard unit-testing
+ * then(spy)
+ * .should(atLeastOnce())
+ * .callSomeMethod(...);
+ *
+ * assertThat(...)...;
+ * }
+ *
+ * #064;Configuration
+ * @Import(ExampleService.class) // A @Component injected with ExampleService
+ * static class Config {
+ * }
+ * }
+ *
+ * If there is more than one bean of the requested type, qualifier metadata must be
+ * specified at field level:
+ * @RunWith(SpringRunner.class)
+ * public class ExampleTests {
+ *
+ * @SpyBeanInjector(type = ExampleService.class, field = "fieldA")
+ * @Qualifier("example")
+ * private ExampleService service;
+ *
+ * ...
+ * }
+ *
+ * @author pengym
+ * @version SpyBeanInjector.java, v 0.1 2023年08月07日 15:38 pengym
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface SpyBeanInjector {
+
+ /**
+ * The name for field which should inject the spy.
+ * When can not find the target field, an {@link IllegalStateException} will be thrown.
+ */
+ String field();
+
+ /**
+ * The name of the bean to inject the spy to a field.
+ * @return the name of the target bean
+ */
+ String name() default "";
+
+ /**
+ * The class type of the bean to inject the spy to a field. This is an alias of {@link #type()} which can be used for
+ * brevity if no other attributes are defined. See {@link #type()} for details.
+ * @return the class type of the target bean
+ */
+ @AliasFor("type")
+ Class> value() default void.class;
+
+ /**
+ * The class type of the bean to inject the spy to a field
+ * @return the class ype of the target bean
+ */
+ @AliasFor("value")
+ Class> type() default void.class;
+
+ /**
+ * The application context id to find the target bean. If not specified, the root application context will be used.
+ *
When can not find the target SOFA module for the specified module name, an {@link IllegalStateException} will be thrown.
+ */
+ String module() default "";
+
+ /**
+ * The reset mode to apply to the spy. The default is {@link MockReset#AFTER}
+ * meaning that spies are automatically reset after each test method is invoked.
+ * @return the reset mode
+ */
+ MockReset reset() default MockReset.AFTER;
+
+ /**
+ * Indicates that Mockito methods such as {@link Mockito#verify(Object) verify(mock)}
+ * should use the {@code target} of AOP advised beans, rather than the proxy itself.
+ * If set to {@code false} you may need to use the result of
+ * {@link org.springframework.test.util.AopTestUtils#getUltimateTargetObject(Object)
+ * AopTestUtils.getUltimateTargetObject(...)} when calling Mockito methods.
+ * @return {@code true} if the target of AOP advised beans is used or {@code false} if
+ * the proxy is used directly
+ */
+ boolean proxyTargetAware() default true;
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/Definition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/Definition.java
new file mode 100644
index 0000000000000000000000000000000000000000..1a6effac597e08443003c24be13ccdea0a5e6eb1
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/Definition.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.definition;
+
+import org.mockito.Mockito;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.core.ResolvableType;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Base class for {@link MockDefinition} or {@link SpyDefinition}
+ *
+ * @author pengym
+ */
+public abstract class Definition {
+
+ private static final int MULTIPLIER = 31;
+
+ private final String name;
+
+ private final ResolvableType type;
+
+ private final String module;
+
+ private final String field;
+
+ private final MockReset reset;
+
+ private final QualifierDefinition qualifier;
+
+ private final ResolvableType mockType;
+
+ protected Object mockInstance;
+
+ public Definition(ResolvableType mockType, String name, ResolvableType type, String module,
+ String field, MockReset reset, QualifierDefinition qualifier) {
+ Assert.notNull(mockType, "MockType must not be null");
+ this.mockType = mockType;
+ this.type = type;
+ this.name = name;
+ this.module = module;
+ this.field = field;
+ this.reset = (reset != null) ? reset : MockReset.AFTER;
+ this.qualifier = qualifier;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ResolvableType getType() {
+ return type;
+ }
+
+ public String getModule() {
+ return module;
+ }
+
+ public String getField() {
+ return field;
+ }
+
+ public MockReset getReset() {
+ return reset;
+ }
+
+ public ResolvableType getMockType() {
+ return this.mockType;
+ }
+
+ public QualifierDefinition getQualifier() {
+ return qualifier;
+ }
+
+ public Object getMockInstance() {
+ return mockInstance;
+ }
+
+ public void resetMock() {
+ if (mockInstance != null) {
+ Mockito.reset(mockInstance);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {
+ return false;
+ }
+ Definition other = (Definition) obj;
+ boolean result = true;
+ result = result && ObjectUtils.nullSafeEquals(this.mockType, other.mockType);
+ result = result && ObjectUtils.nullSafeEquals(this.name, other.name);
+ result = result && ObjectUtils.nullSafeEquals(this.type, other.type);
+ result = result && ObjectUtils.nullSafeEquals(this.module, other.module);
+ result = result && ObjectUtils.nullSafeEquals(this.field, other.field);
+ result = result && ObjectUtils.nullSafeEquals(this.reset, other.reset);
+ result = result && ObjectUtils.nullSafeEquals(this.qualifier, other.qualifier);
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.mockType);
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.name);
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.type);
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.module);
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.field);
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.reset);
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.qualifier);
+ return result;
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/MockDefinition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/MockDefinition.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d7abdd264f92eb8b9cea13f1c4aa1637028506d
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/MockDefinition.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.definition;
+
+import org.mockito.Answers;
+import org.mockito.MockSettings;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * A complete definition that can be used to create a Mockito mock.
+ *
+ * @author pengym
+ * @version MockDefinition.java, v 0.1 2023年08月07日 19:42 pengym
+ */
+public class MockDefinition extends Definition {
+
+ private static final int MULTIPLIER = 31;
+
+ private final Set> extraInterfaces;
+
+ private final Answers answer;
+
+ private final boolean serializable;
+
+ public MockDefinition(ResolvableType resolvableType, String name, ResolvableType type,
+ String module, String field, Class>[] extraInterfaces, Answers answer,
+ boolean serializable, MockReset reset, QualifierDefinition qualifier) {
+ super(resolvableType, name, type, module, field, reset, qualifier);
+ this.extraInterfaces = asClassSet(extraInterfaces);
+ this.answer = (answer != null) ? answer : Answers.RETURNS_DEFAULTS;
+ this.serializable = serializable;
+ }
+
+ public Set> getExtraInterfaces() {
+ return extraInterfaces;
+ }
+
+ public Answers getAnswer() {
+ return answer;
+ }
+
+ public boolean isSerializable() {
+ return serializable;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T createMock() {
+ if (mockInstance == null) {
+ MockSettings settings = MockReset.withSettings(getReset());
+ if (!this.extraInterfaces.isEmpty()) {
+ settings.extraInterfaces(ClassUtils.toClassArray(this.extraInterfaces));
+ }
+ settings.defaultAnswer(this.answer);
+ if (this.serializable) {
+ settings.serializable();
+ }
+ mockInstance = (T) mock(this.getMockType().resolve(), settings);
+ }
+ return (T) mockInstance;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ MockDefinition other = (MockDefinition) obj;
+ boolean result = super.equals(obj);
+ result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces, other.extraInterfaces);
+ result = result && ObjectUtils.nullSafeEquals(this.answer, other.answer);
+ result = result && this.serializable == other.serializable;
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.extraInterfaces);
+ result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.answer);
+ result = MULTIPLIER * result + Boolean.hashCode(this.serializable);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this).append("mockType", this.getMockType())
+ .append("name", this.getName()).append("type", this.getType())
+ .append("module", this.getModule()).append("field", this.getField())
+ .append("extraInterfaces", this.extraInterfaces).append("answer", this.answer)
+ .append("serializable", this.serializable).append("reset", getReset())
+ .append("qualifier", getQualifier()).toString();
+ }
+
+ private Set> asClassSet(Class>[] classes) {
+ Set> classSet = new LinkedHashSet<>();
+ if (classes != null) {
+ classSet.addAll(Arrays.asList(classes));
+ }
+ return Collections.unmodifiableSet(classSet);
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinition.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc895cafa8b98d156e464009e8b190257154d96b
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinition.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.definition;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector;
+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.MergedAnnotations;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * see org.springframework.boot.test.mock.mockito.QualifierDefinition
+ *
+ * @author huzijie
+ * @version QualifierDefinition.java, v 0.1 2023年08月15日 10:57 AM huzijie Exp $
+ */
+public class QualifierDefinition {
+
+ private final Field field;
+
+ private final DependencyDescriptor descriptor;
+
+ private final Set annotations;
+
+ public QualifierDefinition(Field field, Set 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 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);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.annotations.hashCode();
+ }
+
+ public static QualifierDefinition forElement(AnnotatedElement element) {
+ if (element != null && element instanceof Field) {
+ Field field = (Field) element;
+ Set annotations = getQualifierAnnotations(field);
+ if (!annotations.isEmpty()) {
+ return new QualifierDefinition(field, annotations);
+ }
+ }
+ return null;
+ }
+
+ private static Set getQualifierAnnotations(Field field) {
+ // Assume that any annotations other than @MockBean/@SpyBean are qualifiers
+ Annotation[] candidates = field.getDeclaredAnnotations();
+ Set annotations = new HashSet<>(candidates.length);
+ for (Annotation candidate : candidates) {
+ if (!isMockOrSpyAnnotation(candidate.annotationType())) {
+ annotations.add(candidate);
+ }
+ }
+ return annotations;
+ }
+
+ private static boolean isMockOrSpyAnnotation(Class extends Annotation> type) {
+ if (type.equals(MockBeanInjector.class) || type.equals(SpyBeanInjector.class)) {
+ return true;
+ }
+ MergedAnnotations metaAnnotations = MergedAnnotations.from(type);
+ return metaAnnotations.isPresent(MockBeanInjector.class)
+ || metaAnnotations.isPresent(SpyBeanInjector.class);
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinition.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinition.java
new file mode 100644
index 0000000000000000000000000000000000000000..ddf6fb32f055cb7bcb329628fa9c8a89606329d7
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinition.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.definition;
+
+import org.mockito.AdditionalAnswers;
+import org.mockito.MockSettings;
+import org.mockito.Mockito;
+import org.mockito.listeners.VerificationStartedEvent;
+import org.mockito.listeners.VerificationStartedListener;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.test.util.AopTestUtils;
+import org.springframework.util.Assert;
+
+import java.lang.reflect.Proxy;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * A complete definition that can be used to create a Mockito spy.
+ *
+ * @author pengym
+ * @version SpyDefinition.java, v 0.1 2023年08月07日 19:42 pengym
+ */
+public class SpyDefinition extends Definition {
+
+ private static final int MULTIPLIER = 31;
+
+ private final boolean proxyTargetAware;
+
+ public SpyDefinition(ResolvableType resolvableType, String name, ResolvableType type,
+ String module, String field, MockReset reset, boolean proxyTargetAware,
+ QualifierDefinition qualifier) {
+ super(resolvableType, name, type, module, field, reset, qualifier);
+ this.proxyTargetAware = proxyTargetAware;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T createSpy(Object instance) {
+ if (mockInstance == null) {
+ Assert.notNull(instance, "Instance must not be null");
+ Assert.isInstanceOf(this.getMockType().resolve(), instance);
+ if (Mockito.mockingDetails(instance).isSpy()) {
+ return (T) instance;
+ }
+ MockSettings settings = MockReset.withSettings(getReset());
+ if (this.proxyTargetAware) {
+ settings
+ .verificationStartedListeners(new SpyDefinition.SpringAopBypassingVerificationStartedListener());
+ }
+ Class> toSpy;
+ if (Proxy.isProxyClass(instance.getClass())) {
+ settings.defaultAnswer(AdditionalAnswers.delegatesTo(instance));
+ toSpy = this.getMockType().toClass();
+ } else {
+ settings.defaultAnswer(Mockito.CALLS_REAL_METHODS);
+ settings.spiedInstance(instance);
+ toSpy = instance.getClass();
+ }
+ mockInstance = mock(toSpy, settings);
+ }
+ return (T) mockInstance;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ SpyDefinition other = (SpyDefinition) obj;
+ boolean result = super.equals(obj);
+ result = result && this.proxyTargetAware == other.proxyTargetAware;
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = MULTIPLIER * result + Boolean.hashCode(this.proxyTargetAware);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this).append("mockType", this.getMockType())
+ .append("name", this.getName()).append("type", this.getType())
+ .append("module", this.getModule()).append("field", this.getField())
+ .append("proxyTargetAware", this.proxyTargetAware).append("reset", getReset())
+ .append("qualifier", getQualifier()).toString();
+ }
+
+ /**
+ * A {@link VerificationStartedListener} that bypasses any proxy created by Spring AOP
+ * when the verification of a spy starts.
+ */
+ private static final class SpringAopBypassingVerificationStartedListener implements
+ VerificationStartedListener {
+
+ @Override
+ public void onVerificationStarted(VerificationStartedEvent event) {
+ event.setMock(AopTestUtils.getUltimateTargetObject(event.getMock()));
+ }
+
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParser.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParser.java
new file mode 100644
index 0000000000000000000000000000000000000000..7aae84c4650dca8cce10d626cefb8f2e92ea843b
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParser.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.parser;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector;
+import com.alipay.sofa.test.mock.injector.definition.Definition;
+import com.alipay.sofa.test.mock.injector.definition.MockDefinition;
+import com.alipay.sofa.test.mock.injector.definition.QualifierDefinition;
+import com.alipay.sofa.test.mock.injector.definition.SpyDefinition;
+import org.springframework.core.ResolvableType;
+import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
+import org.springframework.test.context.TestContext;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Parser for processing the {@link MockBeanInjector} and {@link SpyBeanInjector} annotations for the testClass
+ *
+ * @author pengym
+ * @version SofaBootTestAnnotationParser.java, v 0.1 2023年08月07日 17:52 pengym
+ */
+public class DefinitionParser {
+
+ private final Set definitions;
+
+ private final Map definitionFields;
+
+ public DefinitionParser() {
+ this.definitions = new LinkedHashSet<>();
+ this.definitionFields = new LinkedHashMap<>();
+ }
+
+ /**
+ * Parse the {@link MockBeanInjector} and {@link SpyBeanInjector} annotations for the testClass
+ *
+ * @param testClass The testClass, see {@link TestContext#getTestClass()}
+ */
+ public void parse(Class> testClass) {
+ Assert.notNull(testClass, "testClass must not be null");
+ ReflectionUtils.doWithFields(testClass, field -> parseTestField(field, testClass));
+ }
+
+ private void parseTestField(Field testField, Class> testClass) {
+ final MergedAnnotations mergedAnnotations = MergedAnnotations.from(testField, SearchStrategy.SUPERCLASS);
+ mergedAnnotations
+ .stream(MockBeanInjector.class)
+ .map(MergedAnnotation::synthesize)
+ .forEach(annotation -> parseSofaMockBeanAnnotation(annotation, testField, testClass));
+
+ mergedAnnotations
+ .stream(SpyBeanInjector.class)
+ .map(MergedAnnotation::synthesize)
+ .forEach(annotation -> parseSofaSpyBeanAnnotation(annotation, testField, testClass));
+ }
+
+ private void parseSofaMockBeanAnnotation(MockBeanInjector annotation, Field field,
+ Class> testClass) {
+ ResolvableType typesToMock = deduceType(field, testClass);
+ MockDefinition mockDefinition = new MockDefinition(typesToMock, annotation.name(),
+ ResolvableType.forClass(annotation.value()), annotation.module(), annotation.field(),
+ annotation.extraInterfaces(), annotation.answer(), annotation.serializable(),
+ annotation.reset(), QualifierDefinition.forElement(field));
+ registerDefinition(mockDefinition, field, "mock");
+ }
+
+ private void registerDefinition(Definition definition, Field field, String type) {
+ boolean isNewDefinition = this.definitions.add(definition);
+ Assert.state(isNewDefinition, () -> "Duplicate " + type + " definition " + definition);
+ this.definitionFields.put(definition, field);
+ }
+
+ private void parseSofaSpyBeanAnnotation(SpyBeanInjector annotation, Field field,
+ Class> testClass) {
+ ResolvableType typesToMock = deduceType(field, testClass);
+ SpyDefinition spyDefinition = new SpyDefinition(typesToMock, annotation.name(),
+ ResolvableType.forClass(annotation.value()), annotation.module(), annotation.field(),
+ annotation.reset(), annotation.proxyTargetAware(),
+ QualifierDefinition.forElement(field));
+ registerDefinition(spyDefinition, field, "spy");
+ }
+
+ private ResolvableType deduceType(Field field, Class> source) {
+ return (field.getGenericType() instanceof java.lang.reflect.TypeVariable) ? ResolvableType
+ .forField(field, source) : ResolvableType.forField(field);
+ }
+
+ public Set getDefinitions() {
+ return Collections.unmodifiableSet(this.definitions);
+ }
+
+ public Field getField(Definition definition) {
+ return this.definitionFields.get(definition);
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolver.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..0852fd357b6fb5994896fccd52c9caa778d2df6c
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolver.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.resolver;
+
+import com.alipay.sofa.boot.constant.SofaBootConstants;
+import com.alipay.sofa.boot.error.ErrorCode;
+import com.alipay.sofa.isle.IsleDeploymentModel;
+import com.alipay.sofa.test.mock.injector.definition.Definition;
+import com.alipay.sofa.test.mock.injector.definition.QualifierDefinition;
+import org.springframework.aop.framework.AopProxyUtils;
+import org.springframework.aop.scope.ScopedProxyUtils;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.ResolvableType;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * A resolve used to find inject target bean and create {@link BeanInjectorStub}.
+ *
+ * @author pengym
+ * @version BeanInjectorResolver.java, v 0.1 2023年08月07日 16:58 pengym
+ */
+public class BeanInjectorResolver {
+
+ private static final String ISLE_MARKER_CLASS = "com.alipay.sofa.isle.ApplicationRuntimeModel";
+
+ private static final boolean ISLE_MODEL_EXIST = ClassUtils.isPresent(
+ ISLE_MARKER_CLASS,
+ null);
+
+ private final ApplicationContext rootApplicationContext;
+
+ private final Map isleApplicationContexts = new LinkedHashMap<>();
+
+ public BeanInjectorResolver(ApplicationContext applicationContext) {
+ this.rootApplicationContext = applicationContext;
+ if (ISLE_MODEL_EXIST) {
+ if (rootApplicationContext.containsBean(SofaBootConstants.APPLICATION)) {
+ IsleDeploymentModel isleDeploymentModel = applicationContext.getBean(
+ SofaBootConstants.APPLICATION, IsleDeploymentModel.class);
+ isleApplicationContexts
+ .putAll(isleDeploymentModel.getModuleApplicationContextMap());
+ }
+ }
+ }
+
+ public BeanInjectorStub resolveStub(Definition definition) {
+ // find target application context
+ ApplicationContext applicationContext = getApplicationContext(definition);
+ ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) applicationContext
+ .getAutowireCapableBeanFactory();
+
+ // find target beanName
+ String beanName = getBeanName(beanFactory, definition);
+
+ // find target bean instance
+ if (!beanFactory.containsBean(beanName)) {
+ throw new IllegalStateException(ErrorCode.convert("01-30005", beanName));
+ }
+ Object bean = resolveTargetObject(beanFactory.getBean(beanName));
+
+ // inject target bean field
+ return injectTargetBeanField(bean, beanName, definition);
+ }
+
+ private ApplicationContext getApplicationContext(Definition definition) {
+ String module = definition.getModule();
+ if (StringUtils.hasText(module)) {
+ ApplicationContext applicationContext = isleApplicationContexts.get(module);
+ if (applicationContext == null) {
+ throw new IllegalStateException(ErrorCode.convert("01-30002", module, definition));
+ }
+ return applicationContext;
+ } else {
+ return rootApplicationContext;
+ }
+ }
+
+ private BeanInjectorStub injectTargetBeanField(Object bean, String beanName,
+ Definition definition) {
+ String fieldName = definition.getField();
+ Field targetField = ReflectionUtils.findField(bean.getClass(), fieldName);
+
+ if (targetField == null) {
+ throw new IllegalStateException("Unable to inject target field to bean " + beanName
+ + ", can not find field " + fieldName + " in "
+ + bean.getClass());
+ }
+
+ BeanInjectorStub beanStubbedField = new BeanInjectorStub(definition, targetField, bean);
+ beanStubbedField.inject();
+ return beanStubbedField;
+ }
+
+ private Object resolveTargetObject(Object obj) {
+ if (!AopUtils.isAopProxy(obj) && !AopUtils.isJdkDynamicProxy(obj)) {
+ return obj;
+ }
+
+ // AopProxy or JdkDynamicProxy
+ return AopProxyUtils.getSingletonTarget(obj);
+ }
+
+ private String getBeanName(ConfigurableListableBeanFactory beanFactory, Definition definition) {
+ if (StringUtils.hasText(definition.getName())) {
+ return definition.getName();
+ }
+ Set existingBeans = getExistingBeans(beanFactory, definition.getType(),
+ definition.getQualifier());
+ if (existingBeans.isEmpty()) {
+ throw new IllegalStateException(ErrorCode.convert("01-30003", definition.getType()));
+ }
+ if (existingBeans.size() == 1) {
+ return existingBeans.iterator().next();
+ }
+ String primaryCandidate = determinePrimaryCandidate(beanFactory, existingBeans,
+ definition.getType());
+ if (primaryCandidate != null) {
+ return primaryCandidate;
+ }
+ throw new IllegalStateException(ErrorCode.convert("01-30004", definition.getType(),
+ existingBeans));
+ }
+
+ private Set getExistingBeans(ConfigurableListableBeanFactory beanFactory,
+ ResolvableType type, QualifierDefinition qualifier) {
+ Set candidates = new TreeSet<>();
+ for (String candidate : getExistingBeans(beanFactory, type)) {
+ if (qualifier == null || qualifier.matches(beanFactory, candidate)) {
+ candidates.add(candidate);
+ }
+ }
+ return candidates;
+ }
+
+ private Set getExistingBeans(ConfigurableListableBeanFactory beanFactory, ResolvableType resolvableType) {
+ Set beans = new LinkedHashSet<>(
+ Arrays.asList(beanFactory.getBeanNamesForType(resolvableType, true, false)));
+ Class> type = resolvableType.resolve(Object.class);
+ String typeName = type.getName();
+ for (String beanName : beanFactory.getBeanNamesForType(FactoryBean.class, true, false)) {
+ beanName = BeanFactoryUtils.transformedBeanName(beanName);
+ BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
+ Object attribute = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
+ if (resolvableType.equals(attribute) || type.equals(attribute) || typeName.equals(attribute)) {
+ beans.add(beanName);
+ }
+ }
+ beans.removeIf(this::isScopedTarget);
+ return beans;
+ }
+
+ private String determinePrimaryCandidate(ConfigurableListableBeanFactory beanFactory,
+ Collection candidateBeanNames,
+ ResolvableType type) {
+ String primaryBeanName = null;
+ for (String candidateBeanName : candidateBeanNames) {
+ BeanDefinition beanDefinition = beanFactory.getBeanDefinition(candidateBeanName);
+ if (beanDefinition.isPrimary()) {
+ if (primaryBeanName != null) {
+ throw new NoUniqueBeanDefinitionException(type.resolve(),
+ candidateBeanNames.size(),
+ "more than one 'primary' bean found among candidates: "
+ + Collections.singletonList(candidateBeanNames));
+ }
+ primaryBeanName = candidateBeanName;
+ }
+ }
+ return primaryBeanName;
+ }
+
+ private boolean isScopedTarget(String beanName) {
+ try {
+ return ScopedProxyUtils.isScopedTarget(beanName);
+ } catch (Throwable ex) {
+ return false;
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStub.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStub.java
new file mode 100644
index 0000000000000000000000000000000000000000..72f63bfd236f68a18669eeb4fd1fa9e5f204f4ba
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStub.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.resolver;
+
+import com.alipay.sofa.boot.error.ErrorCode;
+import com.alipay.sofa.test.mock.injector.definition.Definition;
+import com.alipay.sofa.test.mock.injector.definition.MockDefinition;
+import com.alipay.sofa.test.mock.injector.definition.SpyDefinition;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Field;
+
+/**
+ * A bean injector stub which could transform target field value.
+ *
+ * @author huzijie
+ * @version BeanStubbedField.java, v 0.1 2023年08月17日 7:31 PM huzijie Exp $
+ */
+public class BeanInjectorStub {
+
+ /**
+ * Mock/Spy Definition
+ */
+ private final Definition definition;
+
+ /**
+ * Field to inject
+ */
+ private final Field field;
+
+ /**
+ * The original value of the injected field
+ */
+ private final Object originalValue;
+
+ /**
+ * The bean to inject field
+ */
+ private final Object bean;
+
+ public BeanInjectorStub(Definition definition, Field field, Object bean) {
+ this.definition = definition;
+ this.field = field;
+ this.bean = bean;
+ ReflectionUtils.makeAccessible(field);
+ this.originalValue = ReflectionUtils.getField(field, bean);
+ if (definition instanceof SpyDefinition && this.originalValue == null) {
+ throw new IllegalStateException(ErrorCode.convert("01-30001", field));
+ }
+ }
+
+ /**
+ * Inject the mock/spy to target field.
+ */
+ public void inject() {
+ if (definition instanceof MockDefinition) {
+ ReflectionUtils.setField(field, bean, ((MockDefinition) definition).createMock());
+ } else if (definition instanceof SpyDefinition) {
+ ReflectionUtils.setField(field, bean,
+ ((SpyDefinition) definition).createSpy(originalValue));
+ }
+ }
+
+ /**
+ * Reset the target field value.
+ */
+ public void reset() {
+ ReflectionUtils.setField(field, bean, originalValue);
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/resources/META-INF/spring.factories b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000000000000000000000000000000000000..41b6565dfcd0ecf7659b3222068cebcb04669249
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+# Add TestExecutionListener
+org.springframework.test.context.TestExecutionListener=com.alipay.sofa.test.mock.injector.InjectorMockTestExecutionListener
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockApplicationContextCacheTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockApplicationContextCacheTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..31e89d319659282ca582084c8ce81f088879e190
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockApplicationContextCacheTests.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import org.junit.After;
+import org.junit.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTestContextBootstrapper;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.BootstrapContext;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate;
+import org.springframework.test.context.cache.DefaultContextCache;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Ensure mock not dirty application context cache.
+ *
+ * @author huzijie
+ * @version InjectorMockApplicationContextCacheTests.java, v 0.1 2023年08月21日 7:32 PM huzijie Exp $
+ */
+public class InjectorMockApplicationContextCacheTests {
+
+ private final DefaultContextCache contextCache = new DefaultContextCache();
+
+ private final DefaultCacheAwareContextLoaderDelegate delegate = new DefaultCacheAwareContextLoaderDelegate(
+ this.contextCache);
+
+ @After
+ @SuppressWarnings("unchecked")
+ public void clearCache() {
+ Map contexts = (Map) ReflectionTestUtils
+ .getField(this.contextCache, "contextMap");
+ for (ApplicationContext context : contexts.values()) {
+ if (context instanceof ConfigurableApplicationContext) {
+ ((ConfigurableApplicationContext) context).close();
+ }
+ }
+ this.contextCache.clear();
+ }
+
+ @Test
+ public void useCacheWhenResolveInjectorMockBeanAnnotation() {
+ bootstrapContext(TestClass.class);
+ assertThat(this.contextCache.size()).isOne();
+ bootstrapContext(MockedBeanTestClass.class);
+ assertThat(this.contextCache.size()).isOne();
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void bootstrapContext(Class> testClass) {
+ SpringBootTestContextBootstrapper bootstrapper = new SpringBootTestContextBootstrapper();
+ BootstrapContext bootstrapContext = mock(BootstrapContext.class);
+ given((Class) bootstrapContext.getTestClass()).willReturn(testClass);
+ bootstrapper.setBootstrapContext(bootstrapContext);
+ given(bootstrapContext.getCacheAwareContextLoaderDelegate()).willReturn(this.delegate);
+ TestContext testContext = bootstrapper.buildTestContext();
+ testContext.getApplicationContext();
+ }
+
+ @SpringBootTest(classes = TestConfiguration.class, properties = "spring.application.name=test")
+ static class TestClass {
+
+ }
+
+ @SpringBootTest(classes = TestConfiguration.class, properties = "spring.application.name=test")
+ static class MockedBeanTestClass {
+
+ @MockBeanInjector(field = "testBean", type = InjectBean.class)
+ private TestBean testBean;
+
+ }
+
+ @Configuration
+ static class TestConfiguration {
+
+ @Bean
+ TestBean testBean() {
+ return new TestBean();
+ }
+
+ @Bean
+ InjectBean injectBean() {
+ return new InjectBean();
+ }
+
+ }
+
+ static class TestBean {
+
+ }
+
+ static class InjectBean {
+
+ private TestBean testBean;
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListenerTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListenerTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b53e87cf095073f22add6d4d29685726ae19a5b
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/InjectorMockTestExecutionListenerTests.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import com.alipay.sofa.test.mock.injector.definition.MockDefinition;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.resolver.BeanInjectorStub;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.ResolvableType;
+import org.springframework.test.context.TestContext;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.alipay.sofa.test.mock.injector.InjectorMockTestExecutionListener.STUBBED_DEFINITIONS;
+import static com.alipay.sofa.test.mock.injector.InjectorMockTestExecutionListener.STUBBED_FIELDS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests for {@link InjectorMockTestExecutionListener}.
+ *
+ * @author huzijie
+ * @version InjectorMockTestExecutionListenerTests.java, v 0.1 2023年08月21日 4:41 PM huzijie Exp $
+ */
+public class InjectorMockTestExecutionListenerTests {
+
+ private final InjectorMockTestExecutionListener listener = new InjectorMockTestExecutionListener();
+
+ @Test
+ public void prepareTestInstanceShouldInjectMockBean() {
+ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ TargetBean.class);
+
+ WithMockBean instance = new WithMockBean();
+ TestContext testContext = mockTestContext(instance);
+ given(testContext.getApplicationContext()).willReturn(applicationContext);
+ this.listener.prepareTestInstance(testContext);
+ ExampleService mock = instance.getMockBean();
+ assertThat(mock).isNotNull();
+ assertThat(Mockito.mockingDetails(mock).isMock()).isTrue();
+
+ TargetBean targetBean = applicationContext.getBean(TargetBean.class);
+ ExampleService injectField = targetBean.getFieldA();
+ assertThat(mock).isEqualTo(injectField);
+
+ verify(testContext, times(2)).setAttribute(anyString(), any());
+ }
+
+ @Test
+ public void prepareTestInstanceWhenInjectTargetAlreadyExist() {
+ AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetBean.class);
+
+ WithMockBean instance = new WithMockBean();
+ TestContext testContext = mockTestContext(instance);
+ given(testContext.getApplicationContext()).willReturn(applicationContext);
+ this.listener.prepareTestInstance(testContext);
+ assertThatIllegalStateException().isThrownBy(() -> this.listener.prepareTestInstance(testContext))
+ .withMessageContaining("The existing value");
+ }
+
+ @Test
+ public void beforeTestMethodShouldRestMock() {
+ WithMockBean instance = new WithMockBean();
+ TestContext testContext = mockTestContext(instance);
+
+ MockDefinition definition = createMockDefinition(ExampleService.class, MockReset.BEFORE);
+ ExampleService mock = definition.createMock();
+ when(mock.greeting()).thenReturn("abc");
+ assertThat(mock.greeting()).isEqualTo("abc");
+
+ Set result = new HashSet();
+ result.add(definition);
+ given(testContext.getAttribute(eq(STUBBED_DEFINITIONS))).willReturn(result);
+ this.listener.beforeTestMethod(testContext);
+
+ assertThat(mock.greeting()).isEqualTo(null);
+ }
+
+ @Test
+ public void afterTestMethodShouldRestMock() {
+ WithMockBean instance = new WithMockBean();
+ TestContext testContext = mockTestContext(instance);
+
+ MockDefinition definition = createMockDefinition(ExampleService.class, MockReset.AFTER);
+ ExampleService mock = definition.createMock();
+ when(mock.greeting()).thenReturn("abc");
+ assertThat(mock.greeting()).isEqualTo("abc");
+
+ Set result = new HashSet();
+ result.add(definition);
+ given(testContext.getAttribute(eq(STUBBED_DEFINITIONS))).willReturn(result);
+ this.listener.afterTestMethod(testContext);
+
+ assertThat(mock.greeting()).isEqualTo(null);
+ }
+
+ @Test
+ public void afterTestClassShouldRestInjectStubs() {
+ WithMockBean instance = new WithMockBean();
+ TestContext testContext = mockTestContext(instance);
+
+ BeanInjectorStub beanInjectorStub = mock(BeanInjectorStub.class);
+
+ Set result = new HashSet();
+ result.add(beanInjectorStub);
+ given(testContext.getAttribute(eq(STUBBED_FIELDS))).willReturn(result);
+ this.listener.afterTestMethod(testContext);
+
+ verify(beanInjectorStub, only()).reset();
+ }
+
+ private MockDefinition createMockDefinition(Class> clazz, MockReset mockReset) {
+ return new MockDefinition(ResolvableType.forClass(clazz), null, null, null, null, null,
+ null, false, mockReset, null);
+ }
+
+ private TestContext mockTestContext(Object instance) {
+ TestContext testContext = mock(TestContext.class);
+ given(testContext.getTestInstance()).willReturn(instance);
+ given(testContext.getTestClass()).willReturn((Class) instance.getClass());
+ return testContext;
+ }
+
+ @Configuration
+ static class TargetBean {
+
+ private ExampleService fieldA;
+
+ public ExampleService getFieldA() {
+ return fieldA;
+ }
+ }
+
+ static class WithMockBean {
+
+ public ExampleService getMockBean() {
+ return mockBean;
+ }
+
+ @MockBeanInjector(field = "fieldA", type = TargetBean.class)
+ private ExampleService mockBean;
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/MockDefinitionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/MockDefinitionTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..15388be94b52b6bcba8b699febe88aebe05b6160
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/MockDefinitionTests.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.definition;
+
+import com.alipay.sofa.test.mock.injector.example.ExampleExtraInterface;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mockito;
+import org.mockito.mock.MockCreationSettings;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.core.ResolvableType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link MockDefinition}.
+ *
+ * @author huzijie
+ * @version MockDefinitionTests.java, v 0.1 2023年08月21日 3:30 PM huzijie Exp $
+ */
+public class MockDefinitionTests {
+
+ private static final ResolvableType EXAMPLE_SERVICE_TYPE = ResolvableType
+ .forClass(ExampleService.class);
+
+ @Test
+ public void classToMockMustNotBeNull() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new MockDefinition(null,null,null, null, null,
+ null, null, false, null, null))
+ .withMessageContaining("MockType must not be null");
+ }
+
+ @Test
+ public void createWithDefaults() {
+ MockDefinition definition = new MockDefinition(EXAMPLE_SERVICE_TYPE, null, null, null,
+ "Field", null, null, false, null, null);
+ assertThat(definition.getName()).isNull();
+ assertThat(definition.getModule()).isNull();
+ assertThat(definition.getField()).isEqualTo("Field");
+ assertThat(definition.getMockType()).isEqualTo(EXAMPLE_SERVICE_TYPE);
+ assertThat(definition.getType()).isEqualTo(null);
+ assertThat(definition.getExtraInterfaces()).isEmpty();
+ assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_DEFAULTS);
+ assertThat(definition.isSerializable()).isFalse();
+ assertThat(definition.getReset()).isEqualTo(MockReset.AFTER);
+ assertThat(definition.getQualifier()).isNull();
+ }
+
+ @Test
+ public void createExplicit() {
+ QualifierDefinition qualifier = mock(QualifierDefinition.class);
+ MockDefinition definition = new MockDefinition(EXAMPLE_SERVICE_TYPE, "name",
+ EXAMPLE_SERVICE_TYPE, "Module", "Field",
+ new Class>[] { ExampleExtraInterface.class }, Answers.RETURNS_SMART_NULLS, true,
+ MockReset.BEFORE, qualifier);
+ assertThat(definition.getName()).isEqualTo("name");
+ assertThat(definition.getModule()).isEqualTo("Module");
+ assertThat(definition.getField()).isEqualTo("Field");
+ assertThat(definition.getType()).isEqualTo(EXAMPLE_SERVICE_TYPE);
+ assertThat(definition.getMockType()).isEqualTo(EXAMPLE_SERVICE_TYPE);
+ assertThat(definition.getExtraInterfaces()).containsExactly(ExampleExtraInterface.class);
+ assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_SMART_NULLS);
+ assertThat(definition.isSerializable()).isTrue();
+ assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE);
+ assertThat(definition.getQualifier()).isEqualTo(qualifier);
+ }
+
+ @Test
+ public void createMock() {
+ MockDefinition definition = new MockDefinition(EXAMPLE_SERVICE_TYPE, "name",
+ EXAMPLE_SERVICE_TYPE, "Module", "Field",
+ new Class>[] { ExampleExtraInterface.class }, Answers.RETURNS_SMART_NULLS, true,
+ MockReset.BEFORE, null);
+ ExampleService mock = definition.createMock();
+ MockCreationSettings> settings = Mockito.mockingDetails(mock).getMockCreationSettings();
+ assertThat(mock).isEqualTo(definition.getMockInstance());
+ assertThat(mock).isInstanceOf(ExampleService.class);
+ assertThat(mock).isInstanceOf(ExampleExtraInterface.class);
+ assertThat(settings.getDefaultAnswer()).isEqualTo(Answers.RETURNS_SMART_NULLS);
+ assertThat(settings.isSerializable()).isTrue();
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinitionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinitionTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..2819d91df5298bda2a45039a3b0ad6935be705a8
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/QualifierDefinitionTests.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.definition;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.then;
+
+/**
+ * Tests for {@link QualifierDefinition}.
+ *
+ * @author huzijie
+ * @version QualifierDefinitionTests.java, v 0.1 2023年08月21日 3:30 PM huzijie Exp $
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class QualifierDefinitionTests {
+
+ @Mock
+ private ConfigurableListableBeanFactory beanFactory;
+
+ @Test
+ public void forElementFieldIsNullShouldReturnNull() {
+ assertThat(QualifierDefinition.forElement((Field) null)).isNull();
+ }
+
+ @Test
+ public void forElementWhenElementIsNotFieldShouldReturnNull() {
+ assertThat(QualifierDefinition.forElement(getClass())).isNull();
+ }
+
+ @Test
+ public void forElementWhenElementIsFieldWithNoQualifiersShouldReturnNull() {
+ QualifierDefinition definition = QualifierDefinition.forElement(ReflectionUtils.findField(
+ ConfigA.class, "noQualifier"));
+ assertThat(definition).isNull();
+ }
+
+ @Test
+ public void forElementWhenElementIsFieldWithQualifierShouldReturnDefinition() {
+ QualifierDefinition definition = QualifierDefinition.forElement(ReflectionUtils.findField(
+ ConfigA.class, "directQualifier"));
+ assertThat(definition).isNotNull();
+ }
+
+ @Test
+ public void matchesShouldCallBeanFactory() {
+ Field field = ReflectionUtils.findField(ConfigA.class, "directQualifier");
+ QualifierDefinition qualifierDefinition = QualifierDefinition.forElement(field);
+ qualifierDefinition.matches(this.beanFactory, "bean");
+ then(this.beanFactory).should()
+ .isAutowireCandidate(eq("bean"), argThat(
+ (dependencyDescriptor) -> {
+ assertThat(dependencyDescriptor.getAnnotatedElement()).isEqualTo(field);
+ return true;
+ }));
+ }
+
+ @Test
+ public void applyToShouldSetQualifierElement() {
+ 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() {
+ 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).hasSameHashCodeAs(directQualifier2);
+ assertThat(differentDirectQualifier1).hasSameHashCodeAs(differentDirectQualifier2);
+ assertThat(customQualifier1).hasSameHashCodeAs(customQualifier2);
+ 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(proxyBeanMethods = false)
+ static class ConfigA {
+
+ @MockBeanInjector(field = "Field")
+ private Object noQualifier;
+
+ @MockBeanInjector(field = "Field")
+ @Qualifier("test")
+ private Object directQualifier;
+
+ @MockBeanInjector(field = "Field")
+ @Qualifier("different")
+ private Object differentDirectQualifier;
+
+ @MockBeanInjector(field = "Field")
+ @CustomQualifier
+ private Object customQualifier;
+
+ }
+
+ static class ConfigB {
+
+ @MockBeanInjector(field = "Field")
+ @Qualifier("test")
+ private Object directQualifier;
+
+ @MockBeanInjector(field = "Field")
+ @Qualifier("different")
+ private Object differentDirectQualifier;
+
+ @MockBeanInjector(field = "Field")
+ @CustomQualifier
+ private Object customQualifier;
+
+ }
+
+ @Qualifier
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface CustomQualifier {
+
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinitionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinitionTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..15fd537736e252de57e898a594ab3836e78c1cea
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/definition/SpyDefinitionTests.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.definition;
+
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import com.alipay.sofa.test.mock.injector.example.RealExampleService;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mockito;
+import org.mockito.mock.MockCreationSettings;
+import org.springframework.boot.test.mock.mockito.MockReset;
+import org.springframework.core.ResolvableType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link SpyDefinition}.
+ *
+ * @author huzijie
+ * @version SpyDefinitionTests.java, v 0.1 2023年08月21日 3:31 PM huzijie Exp $
+ */
+public class SpyDefinitionTests {
+
+ private static final ResolvableType REAL_SERVICE_TYPE = ResolvableType
+ .forClass(ExampleService.class);
+
+ @Test
+ public void classToSpyMustNotBeNull() {
+ assertThatIllegalArgumentException().isThrownBy(() -> new SpyDefinition(null, null, null,
+ null, null,null, true, null))
+ .withMessageContaining("MockType must not be null");
+ }
+
+ @Test
+ public void createWithDefaults() {
+ SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, null, null, null, null,
+ null, true, null);
+ assertThat(definition.getName()).isNull();
+ assertThat(definition.getType()).isNull();
+ assertThat(definition.getModule()).isNull();
+ assertThat(definition.getField()).isNull();
+ assertThat(definition.getMockType()).isEqualTo(REAL_SERVICE_TYPE);
+ assertThat(definition.getReset()).isEqualTo(MockReset.AFTER);
+ assertThat(definition.getQualifier()).isNull();
+ }
+
+ @Test
+ public void createExplicit() {
+ QualifierDefinition qualifier = mock(QualifierDefinition.class);
+ SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, "name", REAL_SERVICE_TYPE,
+ "Module", "Field", MockReset.BEFORE, false, qualifier);
+ assertThat(definition.getName()).isEqualTo("name");
+ assertThat(definition.getType()).isEqualTo(REAL_SERVICE_TYPE);
+ assertThat(definition.getModule()).isEqualTo("Module");
+ assertThat(definition.getField()).isEqualTo("Field");
+ assertThat(definition.getMockType()).isEqualTo(REAL_SERVICE_TYPE);
+ assertThat(definition.getReset()).isEqualTo(MockReset.BEFORE);
+ assertThat(definition.getQualifier()).isEqualTo(qualifier);
+ }
+
+ @Test
+ public void createSpy() {
+ SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, "name", REAL_SERVICE_TYPE,
+ "Module", "Field", MockReset.BEFORE, false, null);
+ RealExampleService spy = definition.createSpy(new RealExampleService("hello"));
+ MockCreationSettings> settings = Mockito.mockingDetails(spy).getMockCreationSettings();
+ assertThat(spy).isInstanceOf(ExampleService.class);
+ assertThat(spy).isEqualTo(definition.getMockInstance());
+ assertThat(settings.getDefaultAnswer()).isEqualTo(Answers.CALLS_REAL_METHODS);
+ }
+
+ @Test
+ public void createSpyWhenNullInstanceShouldThrowException() {
+ SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE,
+ "name", REAL_SERVICE_TYPE, "Module", "Field", MockReset.BEFORE, false, null);
+ assertThatIllegalArgumentException().isThrownBy(() -> definition.createSpy(null))
+ .withMessageContaining("Instance must not be null");
+ }
+
+ @Test
+ public void createSpyWhenWrongInstanceShouldThrowException() {
+ SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE,
+ "name", REAL_SERVICE_TYPE, "Module", "Field", MockReset.BEFORE, false, null);
+ assertThatIllegalArgumentException().isThrownBy(() -> definition.createSpy(new ExampleServiceCaller()))
+ .withMessageContaining("must be an instance of");
+ }
+
+ @Test
+ public void createSpyTwice() {
+ SpyDefinition definition = new SpyDefinition(REAL_SERVICE_TYPE, "name", REAL_SERVICE_TYPE,
+ "Module", "Field", MockReset.BEFORE, false, null);
+ Object instance = new RealExampleService("hello");
+ instance = definition.createSpy(instance);
+ assertThat(instance).isEqualTo(definition.createSpy(instance));
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleExtraInterface.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleExtraInterface.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c279a2ec72a575f53150d19d60c2813aa02ae07
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleExtraInterface.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.example;
+
+/**
+ * @author huzijie
+ * @version ExampleExtraInterface.java, v 0.1 2023年08月21日 3:18 PM huzijie Exp $
+ */
+public interface ExampleExtraInterface {
+
+ void doExtra();
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleService.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleService.java
new file mode 100644
index 0000000000000000000000000000000000000000..f31b93e4f7fac78cc75773c2f7cd3fc908097e46
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleService.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.example;
+
+/**
+ * @author huzijie
+ * @version ExampleService.java, v 0.1 2023年08月21日 3:14 PM huzijie Exp $
+ */
+public interface ExampleService {
+
+ String greeting();
+
+ String hello();
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCaller.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCaller.java
new file mode 100644
index 0000000000000000000000000000000000000000..c949a78992ccce51dc4a49ce7a091db748ed77fd
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCaller.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.example;
+
+/**
+ * @author huzijie
+ * @version ExampleServiceCaller.java, v 0.1 2023年08月21日 3:16 PM huzijie Exp $
+ */
+public class ExampleServiceCaller implements ExampleServiceCallerInterface {
+
+ private ExampleService service;
+
+ public ExampleService getService() {
+ return this.service;
+ }
+
+ public void setService(ExampleService service) {
+ this.service = service;
+ }
+
+ @Override
+ public String sayGreeting() {
+ return this.service.greeting();
+ }
+
+ @Override
+ public String sayHello() {
+ return service.hello();
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCallerInterface.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCallerInterface.java
new file mode 100644
index 0000000000000000000000000000000000000000..d3c5727150479f7206b26ee570db7586ca26ccdf
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/ExampleServiceCallerInterface.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.example;
+
+/**
+ * @author huzijie
+ * @version ExampleServiceCallerInterface.java, v 0.1 2023年08月21日 3:16 PM huzijie Exp $
+ */
+public interface ExampleServiceCallerInterface {
+
+ String sayGreeting();
+
+ String sayHello();
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/RealExampleService.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/RealExampleService.java
new file mode 100644
index 0000000000000000000000000000000000000000..7dad35632863771976623b353d35ced515a9cd70
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/example/RealExampleService.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.example;
+
+/**
+ * @author huzijie
+ * @version RealExampleService.java, v 0.1 2023年08月21日 3:16 PM huzijie Exp $
+ */
+public class RealExampleService implements ExampleService {
+
+ private final String greeting;
+
+ public RealExampleService(String greeting) {
+ this.greeting = greeting;
+ }
+
+ @Override
+ public String greeting() {
+ return this.greeting;
+ }
+
+ @Override
+ public String hello() {
+ return "hello";
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToAopProxyBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToAopProxyBeanTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4d3b2ca369f32346c6a090ab8ee26688de1a711
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToAopProxyBeanTests.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCallerInterface;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link MockBeanInjector} with aop proxy bean.
+ *
+ * @author huzijie
+ * @version InjectMockToNormalBeanTests.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectMockToAopProxyBeanTests.Config.class)
+public class InjectMockToAopProxyBeanTests {
+
+ @MockBeanInjector(field = "service", type = ExampleServiceCallerInterface.class)
+ private ExampleService exampleService;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ public void checkMock() {
+ when(exampleService.greeting()).thenReturn("amock");
+ ExampleServiceCallerInterface bean = this.applicationContext
+ .getBean(ExampleServiceCallerInterface.class);
+ assertThat(bean.sayGreeting()).isEqualTo("amock");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ ExampleServiceCallerInterface exampleServiceCaller() {
+ return new ExampleServiceCaller();
+ }
+
+ @Bean
+ BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
+ BeanNameAutoProxyCreator autoProxyCreator = new BeanNameAutoProxyCreator();
+ autoProxyCreator.setBeanNames("exampleServiceCaller");
+ return autoProxyCreator;
+ }
+
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToFactoryBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToFactoryBeanTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..131638965e7af95b1d23a180a0b9e9d2f17dd353
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToFactoryBeanTests.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link MockBeanInjector} with factory bean.
+ *
+ * @author huzijie
+ * @version InjectMockToFactoryBeanTests.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectMockToFactoryBeanTests.Config.class)
+public class InjectMockToFactoryBeanTests {
+
+ @MockBeanInjector(field = "service", type = ExampleServiceCaller.class)
+ private ExampleService exampleService;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ public void checkMock() {
+ when(exampleService.greeting()).thenReturn("amock");
+ ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class);
+ Assertions.assertThat(bean.sayGreeting()).isEqualTo("amock");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ TestFactoryBean testFactoryBean() {
+ return new TestFactoryBean();
+ }
+
+ }
+
+ static class TestFactoryBean implements FactoryBean {
+
+ private ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller();
+
+ @Override
+ public ExampleServiceCaller getObject() {
+ return exampleServiceCaller;
+ }
+
+ @Override
+ public Class> getObjectType() {
+ return ExampleServiceCaller.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return false;
+ }
+
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanExtensionTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanExtensionTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..824cb26880ca4962f2acd1a063faa36c8c7c7951
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanExtensionTests.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+/**
+ * Concrete implementation of {@link InjectMockToGenericBeanTestBase}.
+ *
+ * @author huzijie
+ * @version InjectMockToGenericBeanExtensionTests.java, v 0.1 2023年08月21日 8:19 PM huzijie Exp $
+ */
+public class InjectMockToGenericBeanExtensionTests
+ extends
+ InjectMockToGenericBeanTestBase {
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanTestBase.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanTestBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..018907ed3d43dd7d27714d8e9472969e1fe36046
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToGenericBeanTestBase.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link MockBeanInjector} with generic bean.
+ *
+ * @author huzijie
+ * @version InjectMockToGenericBeanTestBase.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectMockToGenericBeanTestBase.Config.class)
+abstract class InjectMockToGenericBeanTestBase, U extends InjectMockToGenericBeanTestBase.Something> {
+
+ @MockBeanInjector(field = "something", name = "thing")
+ private U something;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void checkMock() {
+ T bean = (T) this.applicationContext.getBean(Thing.class);
+ assertThat(bean.getSomething()).isEqualTo(something);
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ ThingImpl thing() {
+ return new ThingImpl();
+ }
+
+ }
+
+ abstract static class Thing {
+
+ protected T something;
+
+ T getSomething() {
+ return this.something;
+ }
+
+ void setSomething(T something) {
+ this.something = something;
+ }
+
+ }
+
+ static class SomethingImpl extends Something {
+
+ }
+
+ static class ThingImpl extends Thing {
+
+ }
+
+ static class Something {
+
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToNormalBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToNormalBeanTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..f8c6c9f6c3165a27a29b9ab7aff5bfbfeb65cc74
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectMockToNormalBeanTests.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link MockBeanInjector} with normal bean.
+ *
+ * @author huzijie
+ * @version InjectMockToNormalBeanTests.java, v 0.1 2023年08月21日 7:53 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectMockToNormalBeanTests.Config.class)
+public class InjectMockToNormalBeanTests {
+
+ @MockBeanInjector(field = "service", type = ExampleServiceCaller.class)
+ private ExampleService exampleService;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ public void checkMock() {
+ when(exampleService.greeting()).thenReturn("amock");
+ ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class);
+ Assertions.assertThat(bean.sayGreeting()).isEqualTo("amock");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ ExampleServiceCaller exampleServiceCaller() {
+ return new ExampleServiceCaller();
+ }
+
+ }
+
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToAopProxyBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToAopProxyBeanTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..90458773aa5683d32412b658320e5c26de00501a
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToAopProxyBeanTests.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCallerInterface;
+import com.alipay.sofa.test.mock.injector.example.RealExampleService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link SpyBeanInjector} with aop proxy bean.
+ *
+ * @author huzijie
+ * @version InjectSpyToAopProxyBeanTests.java, v 0.1 2023年08月21日 8:24 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectSpyToAopProxyBeanTests.Config.class)
+public class InjectSpyToAopProxyBeanTests {
+
+ @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class)
+ private ExampleService exampleService;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ public void checkSpy() {
+ when(exampleService.greeting()).thenReturn("aspy");
+ ExampleServiceCallerInterface bean = this.applicationContext
+ .getBean(ExampleServiceCallerInterface.class);
+ assertThat(bean.sayGreeting()).isEqualTo("aspy");
+ assertThat(bean.sayHello()).isEqualTo("hello");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ ExampleServiceCallerInterface exampleServiceCaller() {
+ ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller();
+ exampleServiceCaller.setService(new RealExampleService("greeting"));
+ return exampleServiceCaller;
+ }
+
+ @Bean
+ BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
+ BeanNameAutoProxyCreator autoProxyCreator = new BeanNameAutoProxyCreator();
+ autoProxyCreator.setBeanNames("exampleServiceCaller");
+ autoProxyCreator.setProxyTargetClass(true);
+ return autoProxyCreator;
+ }
+
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToFactoryBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToFactoryBeanTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..a25c423e9cccf7912e59c0048b6d463349387d13
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToFactoryBeanTests.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import com.alipay.sofa.test.mock.injector.example.RealExampleService;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link SpyBeanInjector} with factory bean.
+ *
+ * @author huzijie
+ * @version InjectSpyToFactoryBeanTests.java, v 0.1 2023年08月21日 8:24 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectSpyToFactoryBeanTests.Config.class)
+public class InjectSpyToFactoryBeanTests {
+
+ @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class)
+ private ExampleService exampleService;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ public void checkSpy() {
+ when(exampleService.greeting()).thenReturn("aspy");
+ ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class);
+ Assertions.assertThat(bean.sayGreeting()).isEqualTo("aspy");
+ Assertions.assertThat(bean.sayHello()).isEqualTo("hello");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ TestFactoryBean testFactoryBean() {
+ return new TestFactoryBean();
+ }
+
+ }
+
+ static class TestFactoryBean implements FactoryBean {
+
+ private final ExampleServiceCaller exampleServiceCaller;
+
+ public TestFactoryBean() {
+ exampleServiceCaller = new ExampleServiceCaller();
+ exampleServiceCaller.setService(new RealExampleService("greeting"));
+ }
+
+ @Override
+ public ExampleServiceCaller getObject() {
+ return exampleServiceCaller;
+ }
+
+ @Override
+ public Class> getObjectType() {
+ return ExampleServiceCaller.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return false;
+ }
+
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToNormalBeanTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToNormalBeanTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..95dc97b61c5af5a94a1dbf8db5f15a1e268c46f4
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyToNormalBeanTests.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import com.alipay.sofa.test.mock.injector.example.RealExampleService;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link SpyBeanInjector} with normal bean.
+ *
+ * @author huzijie
+ * @version InjectSpyToNormalBeanTests.java, v 0.1 2023年08月21日 8:24 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectSpyToNormalBeanTests.Config.class)
+public class InjectSpyToNormalBeanTests {
+
+ @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class)
+ private ExampleService exampleService;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ public void checkSpy() {
+ when(exampleService.greeting()).thenReturn("aspy");
+ ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class);
+ Assertions.assertThat(bean.sayGreeting()).isEqualTo("aspy");
+ Assertions.assertThat(bean.sayHello()).isEqualTo("hello");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ ExampleServiceCaller exampleServiceCaller() {
+ ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller();
+ exampleServiceCaller.setService(new RealExampleService("greeting"));
+ return exampleServiceCaller;
+ }
+
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyWithJdkProxyTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyWithJdkProxyTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..131deda57adac39c95bb017b168019be9c24b4df
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/InjectSpyWithJdkProxyTests.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.ExampleServiceCaller;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.lang.reflect.Proxy;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link SpyBeanInjector} with a JDK proxy.
+ *
+ * @author huzijie
+ * @version InjectSpyWithJdkProxyTests.java, v 0.1 2023年08月21日 8:35 PM huzijie Exp $
+ */
+@SpringBootTest(classes = TestSofaBootApplication.class)
+@RunWith(SpringRunner.class)
+@Import(InjectSpyWithJdkProxyTests.Config.class)
+public class InjectSpyWithJdkProxyTests {
+
+ @SpyBeanInjector(field = "service", type = ExampleServiceCaller.class)
+ private ExampleService exampleService;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Test
+ public void checkSpy() {
+ when(exampleService.greeting()).thenReturn("aspy");
+ ExampleServiceCaller bean = this.applicationContext.getBean(ExampleServiceCaller.class);
+ Assertions.assertThat(bean.sayGreeting()).isEqualTo("aspy");
+ Assertions.assertThat(bean.sayHello()).isEqualTo("jdkProxy");
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ ExampleServiceCaller exampleServiceCaller() {
+ ExampleServiceCaller exampleServiceCaller = new ExampleServiceCaller();
+ exampleServiceCaller.setService(exampleService());
+ return exampleServiceCaller;
+ }
+
+ private ExampleService exampleService() {
+ return (ExampleService) Proxy.newProxyInstance(getClass().getClassLoader(),
+ new Class>[] { ExampleService.class }, (proxy, method, args) -> "jdkProxy");
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/TestSofaBootApplication.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/TestSofaBootApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..883f80bc71da9d0b224e324559bdc5dfd99c7cf6
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/integration/TestSofaBootApplication.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.integration;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author huzijie
+ * @version TestSofaBootApplication.java, v 0.1 2023年08月17日 8:46 PM huzijie Exp $
+ */
+@SpringBootApplication
+public class TestSofaBootApplication {
+
+ public static void main(String[] args) {
+ SpringApplication application = new SpringApplication();
+ application.run(args);
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParserTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParserTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..1bc410317fb96dd68ebcaf54ddb550d9461ea1d5
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/parser/DefinitionParserTests.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.parser;
+
+import com.alipay.sofa.test.mock.injector.annotation.MockBeanInjector;
+import com.alipay.sofa.test.mock.injector.annotation.SpyBeanInjector;
+import com.alipay.sofa.test.mock.injector.definition.Definition;
+import com.alipay.sofa.test.mock.injector.definition.MockDefinition;
+import com.alipay.sofa.test.mock.injector.definition.SpyDefinition;
+import com.alipay.sofa.test.mock.injector.example.ExampleExtraInterface;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.springframework.boot.test.mock.mockito.MockReset;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+
+/**
+ * Tests for {@link DefinitionParser}.
+ *
+ * @author huzijie
+ * @version DefinitionParserTests.java, v 0.1 2023年08月21日 2:57 PM huzijie Exp $
+ */
+public class DefinitionParserTests {
+
+ private final DefinitionParser parser = new DefinitionParser();
+
+ @Test
+ public void parseSingleMockBeanInjector() {
+ this.parser.parse(SingleMockBeanInjector.class);
+ assertThat(getDefinitions()).hasSize(1);
+ assertThat(getMockDefinition(0).getMockType().resolve()).isEqualTo(ExampleService.class);
+ }
+
+ @Test
+ public void parseMockBeanInjectorAttributes() {
+ this.parser.parse(MockBeanInjectorAttributes.class);
+ assertThat(getDefinitions()).hasSize(1);
+ MockDefinition definition = getMockDefinition(0);
+ assertThat(definition.getField()).isEqualTo("Field");
+ assertThat(definition.getName()).isEqualTo("Name");
+ assertThat(definition.getModule()).isEqualTo("Module");
+ assertThat(definition.getMockType().resolve()).isEqualTo(ExampleService.class);
+ assertThat(definition.getExtraInterfaces()).containsExactly(ExampleExtraInterface.class);
+ assertThat(definition.getAnswer()).isEqualTo(Answers.RETURNS_SMART_NULLS);
+ assertThat(definition.isSerializable()).isTrue();
+ assertThat(definition.getReset()).isEqualTo(MockReset.NONE);
+ assertThat(definition.getQualifier()).isNull();
+ }
+
+ @Test
+ public void parseDuplicateMockBeanInjector() {
+ assertThatIllegalStateException().isThrownBy(() -> this.parser.parse(DuplicateMockBeanInjector.class))
+ .withMessageContaining("Duplicate mock definition");
+ }
+
+ @Test
+ public void parseSingleSpyBeanInjector() {
+ this.parser.parse(SingleSpyBeanInjector.class);
+ assertThat(getDefinitions()).hasSize(1);
+ assertThat(getSpyDefinition(0).getMockType().resolve()).isEqualTo(ExampleService.class);
+ }
+
+ @Test
+ public void parseSpyBeanInjectorAttributes() {
+ this.parser.parse(SpyBeanInjectorAttributes.class);
+ assertThat(getDefinitions()).hasSize(1);
+ SpyDefinition definition = getSpyDefinition(0);
+ assertThat(definition.getField()).isEqualTo("Field");
+ assertThat(definition.getName()).isEqualTo("Name");
+ assertThat(definition.getModule()).isEqualTo("Module");
+ assertThat(definition.getMockType().resolve()).isEqualTo(ExampleService.class);
+ assertThat(definition.getReset()).isEqualTo(MockReset.NONE);
+ assertThat(definition.getQualifier()).isNull();
+ }
+
+ private MockDefinition getMockDefinition(int index) {
+ return (MockDefinition) getDefinitions().get(index);
+ }
+
+ private SpyDefinition getSpyDefinition(int index) {
+ return (SpyDefinition) getDefinitions().get(index);
+ }
+
+ private List getDefinitions() {
+ return new ArrayList<>(this.parser.getDefinitions());
+ }
+
+ static class SingleMockBeanInjector {
+
+ @MockBeanInjector(field = "exampleService", type = ExampleService.class)
+ private ExampleService exampleService;
+
+ }
+
+ static class MockBeanInjectorAttributes {
+
+ @MockBeanInjector(field = "Field", module = "Module", name = "Name", type = ExampleService.class, extraInterfaces = ExampleExtraInterface.class, answer = Answers.RETURNS_SMART_NULLS, serializable = true, reset = MockReset.NONE)
+ private ExampleService exampleService;
+ }
+
+ static class DuplicateMockBeanInjector {
+
+ @MockBeanInjector(field = "exampleService", type = ExampleService.class)
+ private ExampleService exampleServiceA;
+
+ @MockBeanInjector(field = "exampleService", type = ExampleService.class)
+ private ExampleService exampleServiceB;
+ }
+
+ static class SingleSpyBeanInjector {
+
+ @SpyBeanInjector(field = "exampleService", type = ExampleService.class)
+ private ExampleService exampleService;
+ }
+
+ static class SpyBeanInjectorAttributes {
+
+ @SpyBeanInjector(field = "Field", module = "Module", name = "Name", type = ExampleService.class, reset = MockReset.NONE, proxyTargetAware = false)
+ private ExampleService exampleService;
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolverTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolverTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..c6c1b65a0e8cb2bb958781bceee9688ba5fd7697
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorResolverTests.java
@@ -0,0 +1,400 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.resolver;
+
+import com.alipay.sofa.boot.constant.SofaBootConstants;
+import com.alipay.sofa.isle.IsleDeploymentModel;
+import com.alipay.sofa.test.mock.injector.definition.Definition;
+import com.alipay.sofa.test.mock.injector.definition.MockDefinition;
+import com.alipay.sofa.test.mock.injector.definition.QualifierDefinition;
+import com.alipay.sofa.test.mock.injector.definition.SpyDefinition;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.RealExampleService;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.ResolvableType;
+import org.springframework.util.ReflectionUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link BeanInjectorResolver}.
+ *
+ * @author huzijie
+ * @version BeanInjectorResolverTests.java, v 0.1 2023年08月21日 5:35 PM huzijie Exp $
+ */
+public class BeanInjectorResolverTests {
+
+ @Test
+ public void targetModuleExist() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ TargetClass.class);
+ IsleDeploymentModel isleDeploymentModel = mock(IsleDeploymentModel.class);
+ Map map = new HashMap();
+ map.put("testModule", applicationContext);
+ when(isleDeploymentModel.getModuleApplicationContextMap()).thenReturn(map);
+ applicationContext.getBeanFactory().registerSingleton(SofaBootConstants.APPLICATION,
+ isleDeploymentModel);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class),
+ "targetClass", null, "testModule", "exampleService", null, null, false, null, null);
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ TargetClass targetClass = applicationContext.getBean(TargetClass.class);
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue();
+ }
+
+ @Test
+ public void targetModuleNotExist() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(
+ ResolvableType.forClass(ExampleService.class), "targetClass", null, "testModule", "exampleService",
+ null, null, false, null, null);
+ assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .withMessageContaining("Unable to find target module [testModule] when resolve injector");
+ }
+
+ @Test
+ public void findTargetBeanByName() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class),
+ "targetClass", null, null, "exampleService", null, null, false, null, null);
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ TargetClass targetClass = applicationContext.getBean(TargetClass.class);
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue();
+ }
+
+ @Test
+ public void findTargetBeanByNameButNoBeanExist() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(
+ ResolvableType.forClass(ExampleService.class), "noExistBean", null, null, "exampleService",
+ null, null, false, null, null);
+ assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .withMessageContaining("Unable to create bean injector to bean [noExistBean] target bean not exist");
+ }
+
+ @Test
+ public void findTargetBeanByClass() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class),
+ null, ResolvableType.forClass(TargetClass.class), null, "exampleService", null, null,
+ false, null, null);
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ TargetClass targetClass = applicationContext.getBean(TargetClass.class);
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue();
+ }
+
+ @Test
+ public void findTargetBeanByClassButNoBeanExist() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(
+ ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(ExampleService.class), null, "exampleService",
+ null, null, false, null, null);
+ assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .withMessageContaining("expected a single matching bean to injector but no bean found");
+ }
+
+ @Test
+ public void findTargetBeanByClassButNoBeanExistCausedByScope() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScopeTargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(
+ ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(ExampleService.class), null, "exampleService",
+ null, null, false, null, null);
+ assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .withMessageContaining("expected a single matching bean to injector but no bean found");
+ }
+
+ @Test
+ public void findTargetBeanByClassButMultiBeanFound() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(MultiTargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(
+ ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(TargetClass.class), null, "exampleService",
+ null, null, false, null, null);
+ assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .withMessageContaining("expected a single matching bean to injector but found [targetClassA, targetClassB]");
+ }
+
+ @Test
+ public void findTargetBeanByClassWithQualifier() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ MultiTargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class),
+ null, ResolvableType.forClass(TargetClass.class), null, "exampleService", null, null,
+ false, null, QualifierDefinition.forElement(ReflectionUtils.findField(
+ QualifierClass.class, "targetClassField")));
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ TargetClass targetClass = applicationContext.getBean("targetClassA", TargetClass.class);
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue();
+ }
+
+ @Test
+ public void findTargetBeanByClassWithPrimary() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ OnePrimaryTargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class),
+ null, ResolvableType.forClass(TargetClass.class), null, "exampleService", null, null,
+ false, null, null);
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ TargetClass targetClass = applicationContext.getBean("targetClassA", TargetClass.class);
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue();
+ }
+
+ @Test
+ public void findTargetBeanByClassButMultiPrimaryBeanFound() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(MultiPrimaryTargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(
+ ResolvableType.forClass(ExampleService.class), null, ResolvableType.forClass(TargetClass.class), null, "exampleService",
+ null, null, false, null, null);
+ assertThatThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .isInstanceOf(NoUniqueBeanDefinitionException.class)
+ .hasMessageContaining("more than one 'primary' bean found among candidates: [[targetClassA, targetClassB]]");
+ }
+
+ @Test
+ public void targetFieldCannotBeFound() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(
+ ResolvableType.forClass(ExampleService.class), "targetClass", null, null, "exampleServiceA",
+ null, null, false, null, null);
+ assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .withMessageContaining("Unable to inject target field to bean targetClass, can not find field exampleServiceA in class com.alipay.sofa.test.mock.injector.resolver.BeanInjectorResolverTests$TargetClass");
+ }
+
+ @Test
+ public void jdkProxyBeanInject() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ JdkProxyTargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class),
+ "targetClass", null, null, "exampleService", null, null, false, null, null);
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ TargetInterface targetClass = applicationContext.getBean(TargetInterface.class);
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue();
+ }
+
+ @Test
+ public void cglibProxyBeanInject() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ CglibProxyTargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new MockDefinition(ResolvableType.forClass(ExampleService.class),
+ "targetClass", null, null, "exampleService", null, null, false, null, null);
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ TargetInterface targetClass = applicationContext.getBean(TargetInterface.class);
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isMock()).isTrue();
+ }
+
+ @Test
+ public void spyTargetBeanWhenFieldIsNull() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+
+ Definition definition = new SpyDefinition(ResolvableType.forClass(ExampleService.class),
+ "targetClass", null, null, "exampleService", null, false, null);
+ assertThatIllegalStateException().isThrownBy(() -> beanInjectorResolver.resolveStub(definition))
+ .withMessageContaining("Unable to create spy to inject target field private com.alipay.sofa.test.mock.injector.example.ExampleService com.alipay.sofa.test.mock.injector.resolver.BeanInjectorResolverTests$TargetClass.exampleService when origin value is null");
+ }
+
+ @Test
+ public void spyTargetBean() {
+ GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(
+ TargetClass.class);
+ BeanInjectorResolver beanInjectorResolver = new BeanInjectorResolver(applicationContext);
+ TargetClass targetClass = applicationContext.getBean(TargetClass.class);
+ targetClass.setExampleService(new RealExampleService("test"));
+
+ Definition definition = new SpyDefinition(ResolvableType.forClass(ExampleService.class),
+ "targetClass", null, null, "exampleService", null, false, null);
+ BeanInjectorStub stub = beanInjectorResolver.resolveStub(definition);
+ stub.inject();
+
+ assertThat(Mockito.mockingDetails(targetClass.getExampleService()).isSpy()).isTrue();
+ }
+
+ interface TargetInterface {
+
+ ExampleService getExampleService();
+
+ }
+
+ @Configuration(value = "targetClass", proxyBeanMethods = false)
+ static class TargetClass implements TargetInterface {
+
+ private ExampleService exampleService;
+
+ public ExampleService getExampleService() {
+ return exampleService;
+ }
+
+ public void setExampleService(ExampleService exampleService) {
+ this.exampleService = exampleService;
+ }
+ }
+
+ @Configuration
+ static class MultiTargetClass {
+
+ @Bean
+ public TargetClass targetClassA() {
+ return new TargetClass();
+ }
+
+ @Bean
+ public TargetClass targetClassB() {
+ return new TargetClass();
+ }
+ }
+
+ static class QualifierClass {
+
+ @Qualifier("targetClassA")
+ private TargetClass targetClassField;
+ }
+
+ @Configuration
+ static class ScopeTargetClass {
+
+ @Bean
+ @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
+ public TargetClass targetClass() {
+ return new TargetClass();
+ }
+ }
+
+ @Configuration
+ static class OnePrimaryTargetClass {
+
+ @Bean
+ @Primary
+ public TargetClass targetClassA() {
+ return new TargetClass();
+ }
+
+ @Bean
+ public TargetClass targetClassB() {
+ return new TargetClass();
+ }
+ }
+
+ @Configuration
+ static class MultiPrimaryTargetClass {
+
+ @Bean
+ @Primary
+ public TargetClass targetClassA() {
+ return new TargetClass();
+ }
+
+ @Bean
+ @Primary
+ public TargetClass targetClassB() {
+ return new TargetClass();
+ }
+ }
+
+ @Configuration
+ static class JdkProxyTargetClass {
+
+ @Bean
+ public TargetInterface targetClass() {
+ return new TargetClass();
+ }
+
+ @Bean
+ public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
+ BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
+ beanNameAutoProxyCreator.setBeanNames("targetClass");
+ beanNameAutoProxyCreator.setProxyTargetClass(false);
+ return beanNameAutoProxyCreator;
+ }
+ }
+
+ @Configuration
+ static class CglibProxyTargetClass {
+
+ @Bean
+ public TargetInterface targetClass() {
+ return new TargetClass();
+ }
+
+ @Bean
+ public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
+ BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
+ beanNameAutoProxyCreator.setBeanNames("targetClass");
+ beanNameAutoProxyCreator.setProxyTargetClass(true);
+ return beanNameAutoProxyCreator;
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStubTests.java b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStubTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..f16381beb1e0b7682cfb7a4d9927bc1c20d3896f
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/java/com/alipay/sofa/test/mock/injector/resolver/BeanInjectorStubTests.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.test.mock.injector.resolver;
+
+import com.alipay.sofa.test.mock.injector.definition.MockDefinition;
+import com.alipay.sofa.test.mock.injector.definition.SpyDefinition;
+import com.alipay.sofa.test.mock.injector.example.ExampleService;
+import com.alipay.sofa.test.mock.injector.example.RealExampleService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Field;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link BeanInjectorStub}.
+ *
+ * @author huzijie
+ * @version BeanInjectorStubTests.java, v 0.1 2023年08月21日 4:27 PM huzijie Exp $
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class BeanInjectorStubTests {
+
+ @Mock
+ private MockDefinition mockDefinition;
+
+ @Mock
+ private SpyDefinition spyDefinition;
+
+ private final Field field = ReflectionUtils.findField(TargetClass.class,
+ "exampleService");
+
+ private final ExampleService exampleService = new RealExampleService("real");
+
+ @Test
+ public void mockBeanInjectorStub() {
+ TargetClass targetClass = new TargetClass();
+ BeanInjectorStub beanInjectorStub = new BeanInjectorStub(mockDefinition, field, targetClass);
+ assertThat(targetClass.getExampleService()).isNull();
+
+ when(mockDefinition.createMock()).thenReturn(exampleService);
+ beanInjectorStub.inject();
+
+ assertThat(targetClass.getExampleService()).isEqualTo(exampleService);
+
+ beanInjectorStub.reset();
+ assertThat(targetClass.getExampleService()).isNull();
+ }
+
+ @Test
+ public void spyBeanInjectorStub() {
+ TargetClass targetClass = new TargetClass();
+ RealExampleService realExampleService = new RealExampleService("real");
+ targetClass.setExampleService(realExampleService);
+ BeanInjectorStub beanInjectorStub = new BeanInjectorStub(spyDefinition, field, targetClass);
+
+ when(spyDefinition.createSpy(any())).thenReturn(exampleService);
+ beanInjectorStub.inject();
+
+ assertThat(targetClass.getExampleService()).isEqualTo(exampleService);
+
+ beanInjectorStub.reset();
+ assertThat(targetClass.getExampleService()).isEqualTo(realExampleService);
+ }
+
+ static class TargetClass {
+
+ public void setExampleService(ExampleService exampleService) {
+ this.exampleService = exampleService;
+ }
+
+ private ExampleService exampleService;
+
+ public ExampleService getExampleService() {
+ return exampleService;
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/config/application.properties b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/config/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..8a067cade5fa6c5f9aa10d4aa46ffff6f21378a6
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/config/application.properties
@@ -0,0 +1,4 @@
+spring.application.name=smoke-tests-test
+logging.path=./logs
+
+
diff --git a/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/logback.xml b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/logback.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9b6f953223f61ad3869a16ef9133f33701da3ae4
--- /dev/null
+++ b/sofa-boot-project/sofa-boot-core/test-sofa-boot/src/test/resources/logback.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreator.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreator.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c2ee76a290858e8d13a7d85f27b39381b26cf39
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreator.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.aop.framework.autoproxy;
+
+import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.PatternMatchUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Extension for {@link BeanNameAutoProxyCreator} to support exclude specify bean names.
+ *
+ * @author huzijie
+ * @version ExcludeBeanNameAutoProxyCreator.java, v 0.1 2024年01月04日 4:24 PM huzijie Exp $
+ */
+public class ExcludeBeanNameAutoProxyCreator extends BeanNameAutoProxyCreator {
+
+ @Nullable
+ private List excludeBeanNames;
+
+ /**
+ * Set the names of the beans that should not automatically get wrapped with proxies.
+ * A name can specify a prefix to match by ending with "*", e.g. "myBean,tx*"
+ * will match the bean named "myBean" and all beans whose name start with "tx".
+ * NOTE: In case of a FactoryBean, only the objects created by the
+ * FactoryBean will get proxied. This default behavior applies as of Spring 2.0.
+ * If you intend to proxy a FactoryBean instance itself (a rare use case, but
+ * Spring 1.2's default behavior), specify the bean name of the FactoryBean
+ * including the factory-bean prefix "&": e.g. "&myFactoryBean".
+ * @see org.springframework.beans.factory.FactoryBean
+ * @see org.springframework.beans.factory.BeanFactory#FACTORY_BEAN_PREFIX
+ */
+ public void setExcludeBeanNames(String... beanNames) {
+ Assert.notEmpty(beanNames, "'excludeBeanNames' must not be empty");
+ this.excludeBeanNames = new ArrayList<>(beanNames.length);
+ for (String mappedName : beanNames) {
+ this.excludeBeanNames.add(StringUtils.trimWhitespace(mappedName));
+ }
+ }
+
+ @Override
+ protected boolean isMatch(String beanName, String mappedName) {
+ return super.isMatch(beanName, mappedName) && !isExcluded(beanName);
+ }
+
+ private boolean isExcluded(String beanName) {
+ if (excludeBeanNames != null) {
+ for (String mappedName : this.excludeBeanNames) {
+ if (PatternMatchUtils.simpleMatch(mappedName, beanName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractJarVersionVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractJarVersionVerifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..bb51de040e781e38b2a945b124da436c21ea6c34
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractJarVersionVerifier.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.compatibility;
+
+import org.springframework.core.env.Environment;
+
+import java.util.Collection;
+
+/**
+ * Abstract class for {@link AbstractSwitchableCompatibilityVerifier} to verify jar compatibility.
+ *
+ * @author huzijie
+ * @version AbstractJarVersionVerifier.java, v 0.1 2023年08月03日 5:14 PM huzijie Exp $
+ */
+public abstract class AbstractJarVersionVerifier extends AbstractSwitchableCompatibilityVerifier {
+
+ public AbstractJarVersionVerifier(Environment environment) {
+ super(environment);
+ }
+
+ @Override
+ public CompatibilityPredicate compatibilityPredicate() {
+ return () -> {
+ Collection compatibilityPredicates = getJarCompatibilityPredicates();
+ if (compatibilityPredicates == null) {
+ return true;
+ }
+ return compatibilityPredicates.stream().allMatch(CompatibilityPredicate::isCompatible);
+ };
+ }
+
+ @Override
+ public String errorDescription() {
+ return String.format("SOFABoot is not compatible with jar [%s] for current version.",
+ name());
+ }
+
+ @Override
+ public String action() {
+ return String.format(
+ "Change [%s] to appropriate version,"
+ + "you can visit this doc [%s] and find an appropriate version,"
+ + "If you want to disable this check, just set the property [%s=false].",
+ name(), doc(), this.enableKey);
+ }
+
+ public abstract Collection getJarCompatibilityPredicates();
+
+ public abstract String name();
+
+ public abstract String doc();
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractSwitchableCompatibilityVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractSwitchableCompatibilityVerifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..a005ec28ce420b1ebbc237482d2a3791b5db9930
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/AbstractSwitchableCompatibilityVerifier.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.compatibility;
+
+import org.springframework.core.env.Environment;
+
+/**
+ * Abstract class for {@link CompatibilityVerifier} to support switch.
+ *
+ * @author huzijie
+ * @version AbstractSwitchableCompatibilityVerifier.java, v 0.1 2023年08月03日 6:10 PM huzijie Exp $
+ */
+public abstract class AbstractSwitchableCompatibilityVerifier implements CompatibilityVerifier {
+
+ private static final String ENABLE_KEY_FORMAT = "sofa.boot.compatibility-verifier.%s.enabled";
+
+ protected final Environment environment;
+
+ protected String enableKey;
+
+ public AbstractSwitchableCompatibilityVerifier(Environment environment) {
+ this.environment = environment;
+ }
+
+ @Override
+ public VerificationResult verify() {
+ this.enableKey = String.format(ENABLE_KEY_FORMAT, enableKey());
+ if (!Boolean.parseBoolean(environment.getProperty(enableKey, "true"))) {
+ return VerificationResult.compatible();
+ }
+
+ CompatibilityPredicate compatibilityPredicate = compatibilityPredicate();
+ boolean matches = compatibilityPredicate.isCompatible();
+ if (matches) {
+ return VerificationResult.compatible();
+ }
+ return VerificationResult.notCompatible(errorDescription(), action());
+ }
+
+ public abstract CompatibilityPredicate compatibilityPredicate();
+
+ public abstract String errorDescription();
+
+ public abstract String action();
+
+ public abstract String enableKey();
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityNotMetException.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityNotMetException.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9dc5778d6bd0a2434366a6f127bd8ac688f1eb7
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityNotMetException.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.compatibility;
+
+import java.util.List;
+
+/**
+ * Exception for not compatibility met.
+ *
+ * @author huzijie
+ * @version CompatibilityNotMetException.java, v 0.1 2023年08月03日 4:40 PM huzijie Exp $
+ */
+public class CompatibilityNotMetException extends RuntimeException {
+
+ final List results;
+
+ CompatibilityNotMetException(List results, String errorMessage) {
+ super("Compatibility checks have failed: " + errorMessage);
+ this.results = results;
+ }
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityPredicate.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityPredicate.java
new file mode 100644
index 0000000000000000000000000000000000000000..d5d43745b2b19e3361a56efc456b14558dc1f351
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityPredicate.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.compatibility;
+
+/**
+ * Interface for Predicate compatibility verify result, for form spring cloud.
+ *
+ * @author huzijie
+ * @version CompatibilityPredicate.java, v 0.1 2023年08月03日 4:35 PM huzijie Exp $
+ */
+public interface CompatibilityPredicate {
+
+ /**
+ * whether is compatible
+ * @return compatible result
+ */
+ boolean isCompatible();
+
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityVerifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..6310da1ad87f5203f7c6664b3d6b81b969fc39b1
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompatibilityVerifier.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.compatibility;
+
+/**
+ * Interface for compatibility verifier, for form spring cloud.
+ *
+ * @author huzijie
+ * @version CompatibilityVerifier.java, v 0.1 2023年08月03日 4:08 PM huzijie Exp $
+ */
+public interface CompatibilityVerifier {
+
+ /**
+ * verify compatibility
+ * @return verify result
+ */
+ VerificationResult verify();
+
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompositeCompatibilityVerifier.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompositeCompatibilityVerifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..b1d6d382c5cc499898f9ddae26211f7dacf73669
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/CompositeCompatibilityVerifier.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.compatibility;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Composite compatibility verifier.
+ *
+ * @author huzijie
+ * @version CompositeCompatibilityVerifier.java, v 0.1 2023年08月03日 4:40 PM huzijie Exp $
+ */
+public class CompositeCompatibilityVerifier {
+
+ private final List verifiers;
+
+ public CompositeCompatibilityVerifier(List verifiers) {
+ this.verifiers = verifiers;
+ }
+
+ public void verifyCompatibilities() {
+ List errors = verifierErrors();
+ if (errors.isEmpty()) {
+ return;
+ }
+ String errorMessage = errors.stream().map(VerificationResult::toErrorMessage).collect(Collectors.toList()).toString();
+ throw new CompatibilityNotMetException(errors, errorMessage);
+ }
+
+ private List verifierErrors() {
+ List errors = new ArrayList<>();
+ for (CompatibilityVerifier verifier : this.verifiers) {
+ VerificationResult result = verifier.verify();
+ if (result.isNotCompatible()) {
+ errors.add(result);
+ }
+ }
+ return errors;
+ }
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/VerificationResult.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/VerificationResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..b011c26bf5d99e5bf2851b293b49e6d30f2149ab
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/compatibility/VerificationResult.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.compatibility;
+
+import org.springframework.util.StringUtils;
+
+import java.util.Objects;
+
+/**
+ * Verification result.
+ *
+ * @author huzijie
+ * @version VerificationResult.java, v 0.1 2023年08月03日 4:08 PM huzijie Exp $
+ */
+public class VerificationResult {
+
+ private final String description;
+
+ private final String action;
+
+ // if OK
+ private VerificationResult() {
+ this.description = "";
+ this.action = "";
+ }
+
+ // if not OK
+ private VerificationResult(String errorDescription, String action) {
+ this.description = errorDescription;
+ this.action = action;
+ }
+
+ public static VerificationResult compatible() {
+ return new VerificationResult();
+ }
+
+ public static VerificationResult notCompatible(String errorDescription, String action) {
+ return new VerificationResult(errorDescription, action);
+ }
+
+ public boolean isNotCompatible() {
+ return StringUtils.hasText(this.description) || StringUtils.hasText(this.action);
+ }
+
+ public String toErrorMessage() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("\n");
+ stringBuilder.append("VerificationResult:");
+ stringBuilder.append("\n");
+ stringBuilder.append("—— description: ");
+ stringBuilder.append(description);
+ stringBuilder.append("\n");
+ stringBuilder.append("—— action: ");
+ stringBuilder.append(action);
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof VerificationResult)) {
+ return false;
+ }
+ VerificationResult that = (VerificationResult) o;
+ return description.equals(that.description) && action.equals(that.action);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(description, action);
+ }
+}
diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java
index f730cf5611897f6b72d950a85626e848c36bea55..356f5db7caf4d9ad8d469bcff473428b3f6b578d 100644
--- a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java
+++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/listener/SofaConfigSourceSupportListener.java
@@ -25,6 +25,8 @@ import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.StringUtils;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/**
* add a config source based on {@link ConfigurableEnvironment}
* @author huzijie
@@ -34,32 +36,40 @@ public class SofaConfigSourceSupportListener
implements
ApplicationListener,
Ordered {
- private static final int SOFA_BOOT_CONFIG_SOURCE_ORDER = ApplicationListenerOrderConstants.SOFA_CONFIG_SOURCE_SUPPORT_LISTENER_ORDER;
+ private static final int SOFA_BOOT_CONFIG_SOURCE_ORDER = ApplicationListenerOrderConstants.SOFA_CONFIG_SOURCE_SUPPORT_LISTENER_ORDER;
+
+ private final AtomicBoolean registered = new AtomicBoolean();
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
- ConfigurableEnvironment environment = event.getEnvironment();
- SofaConfigs.addConfigSource(new AbstractConfigSource() {
- @Override
- public int getOrder() {
- return SOFA_BOOT_CONFIG_SOURCE_ORDER;
- }
+ registerSofaConfigs(event.getEnvironment());
+ }
+
+ private void registerSofaConfigs(ConfigurableEnvironment environment) {
+ if (registered.compareAndSet(false, true)) {
+ SofaConfigs.addConfigSource(new AbstractConfigSource() {
+
+ @Override
+ public int getOrder() {
+ return SOFA_BOOT_CONFIG_SOURCE_ORDER;
+ }
- @Override
- public String getName() {
- return "SOFABootEnv";
- }
+ @Override
+ public String getName() {
+ return "SOFABootEnv";
+ }
- @Override
- public String doGetConfig(String key) {
- return environment.getProperty(key);
- }
+ @Override
+ public String doGetConfig(String key) {
+ return environment.getProperty(key);
+ }
- @Override
- public boolean hasKey(String key) {
- return !StringUtils.isEmpty(environment.getProperty(key));
- }
- });
+ @Override
+ public boolean hasKey(String key) {
+ return !StringUtils.isEmpty(environment.getProperty(key));
+ }
+ });
+ }
}
@Override
diff --git a/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties b/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties
index f6744ac735e74745723223d0069421d26e5f51c9..5920a0a727ea646fc7f48a20d5f6025bb84f0a72 100644
--- a/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties
+++ b/sofa-boot-project/sofa-boot/src/main/resources/sofa-boot/log-codes.properties
@@ -2,7 +2,7 @@
#Runtime error
##SOFA service error
-01-00000=Must contains the target object whiling registering Service
+01-00000=Must contain the target object whiling registering Service
01-00001=Can't find BindingAdapter of type %s while registering service %s
01-00002=PreOut Binding [%s] for [%s] occur exception
01-00003=PreOut Binding [%s] occur exception
@@ -18,9 +18,10 @@
01-00102=Unable to get implementation of reference component, there's some error occurred when register this reference component
01-00103=Bean [%s] of type [%s] has already annotated by @SofaService can not be registered using xml. Please check it
01-00104=Bean [%s] type is [%s] not isAssignableFrom [%s] , please check it
-01-00200=Can not found binding converter for binding type %s
+01-00200=Can not find binding converter for binding type %s
01-00201=Interface type is null. Interface type is required while publish a service
01-00202=Argument delay must be a positive integer or zero
+01-00203=SofaService [%s] was already registered, please remove the duplicate registration
01-00400=JVM Reference[%s#%s] can not find the corresponding JVM service. Please check if there is a SOFA deployment publish the corresponding JVM service. If this exception occurred when the application starts up, please add Require-Module to SOFA deployment's MANIFEST.MF to indicate the startup dependency of SOFA modules
##Extension error
@@ -58,7 +59,7 @@
01-11004=Interrupted when wait for Spring Application Context refresh
01-11005=Error occurred when get Spring Application Context refresh future
01-11006=Cannot register module deployment for module name '[%s]': replacing '[%s]' with '[%s]'
-01-11007=Some module context(s) %s failed to refresh, please see /logs/sofa-runtime/common-error.log for further information, you could set 'com.alipay.sofa.boot.ignoreModuleInstallFailure=true' to ignore it
+01-11007=Some module context(s) %s failed to refresh, please see /logs/sofa-runtime/common-error.log to find root cause
##Module dependency error
01-12000=Modules that could not install(Mainly due to module dependency not satisfied)
@@ -99,6 +100,14 @@
01-24001=SOFABoot ReadinessCheckCallback[%s] check failed, the details is: %s
01-24002=Error occurred while doing ReadinessCheckCallback[%s] check
+#Test component error
+01-30000=The existing value '%s' of field '%s' is not the same as the new value '%s'
+01-30001=Unable to create spy to inject target field %s when origin value is null
+01-30002=Unable to find target module [%s] when resolve injector: %s
+01-30003=Unable to create bean injector to bean by type [%s] expected a single matching bean to injector but no bean found
+01-30004=Unable to create bean injector to bean by type [%s] expected a single matching bean to injector but found %s
+01-30005=Unable to create bean injector to bean [%s] target bean not exist
+
diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreatorTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreatorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..083e0aa20867b73eb3a2a2519f7b9e5cdf9070c8
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/aop/framework/autoproxy/ExcludeBeanNameAutoProxyCreatorTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.aop.framework.autoproxy;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ExcludeBeanNameAutoProxyCreator}.
+ *
+ * @author huzijie
+ * @version ExcludeBeanNameAutoProxyCreatorTests.java, v 0.1 2024年01月04日 4:36 PM huzijie Exp $
+ */
+public class ExcludeBeanNameAutoProxyCreatorTest {
+
+ @Test
+ public void excludeBeanNames() {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
+ ExcludeBeanNameAutoProxyCreatorTestConfiguration.class);
+ SampleInterface sampleA = context.getBean("sampleA", SampleInterface.class);
+ SampleInterface sampleB = context.getBean("sampleBeanB", SampleInterface.class);
+ SampleInterface sampleC = context.getBean("sampleBeanC", SampleInterface.class);
+ assertThat(sampleA.hello()).isEqualTo("hello");
+ assertThat(sampleB.hello()).isEqualTo("aop");
+ assertThat(sampleC.hello()).isEqualTo("hello");
+ }
+
+ @Configuration
+ static class ExcludeBeanNameAutoProxyCreatorTestConfiguration {
+
+ @Bean
+ public SampleInterface sampleA() {
+ return new SampleInterfaceImpl();
+ }
+
+ @Bean
+ public SampleInterface sampleBeanB() {
+ return new SampleInterfaceImpl();
+ }
+
+ @Bean
+ public SampleInterface sampleBeanC() {
+ return new SampleInterfaceImpl();
+ }
+
+ @Bean
+ public ExcludeBeanNameAutoProxyCreator excludeBeanNameAutoProxyCreator() {
+ ExcludeBeanNameAutoProxyCreator autoProxyCreator = new ExcludeBeanNameAutoProxyCreator();
+ autoProxyCreator.setBeanNames("sampleBean*");
+ autoProxyCreator.setExcludeBeanNames("sampleBeanC");
+ autoProxyCreator.setInterceptorNames("sampleAdvisor");
+ return autoProxyCreator;
+ }
+
+ @Bean
+ public MethodInterceptor sampleAdvisor() {
+ return new MethodInterceptor() {
+ @Nullable
+ @Override
+ public Object invoke(@Nonnull MethodInvocation invocation) {
+ return "aop";
+ }
+ };
+ }
+ }
+
+ interface SampleInterface {
+
+ String hello();
+ }
+
+ static class SampleInterfaceImpl implements SampleInterface {
+
+ @Override
+ public String hello() {
+ return "hello";
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractJarVersionVerifierTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractJarVersionVerifierTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4ffd03a0eaea9de439c0c317f31cf0e1e5fd377
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractJarVersionVerifierTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.test.compatibility;
+
+import com.alipay.sofa.boot.compatibility.AbstractJarVersionVerifier;
+import com.alipay.sofa.boot.compatibility.CompatibilityPredicate;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.core.env.Environment;
+import org.springframework.mock.env.MockEnvironment;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author huzijie
+ * @version AbstractJarVersionVerifierTest.java, v 0.1 2023年08月07日 12:07 PM huzijie Exp $
+ */
+public class AbstractJarVersionVerifierTest {
+
+ private final MockEnvironment mockEnvironment = new MockEnvironment();
+
+ @Test
+ public void testJar() {
+ TestJarVersionVerifier verifier = new TestJarVersionVerifier(mockEnvironment);
+ Assert.assertTrue(verifier.verify().isNotCompatible());
+ }
+
+ public static class TestJarVersionVerifier extends AbstractJarVersionVerifier {
+
+ public TestJarVersionVerifier(Environment environment) {
+ super(environment);
+ }
+
+ @Override
+ public Collection getJarCompatibilityPredicates() {
+ List list = new ArrayList<>();
+ list.add(() -> false);
+ list.add(() -> true);
+ return list;
+ }
+
+ @Override
+ public String name() {
+ return "test jar";
+ }
+
+ @Override
+ public String doc() {
+ return "test doc";
+ }
+
+ @Override
+ public String enableKey() {
+ return "test";
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractSwitchableCompatibilityVerifierTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractSwitchableCompatibilityVerifierTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ab83c77adef1af28c34fff703b7a140d284b125
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/AbstractSwitchableCompatibilityVerifierTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.test.compatibility;
+
+import com.alipay.sofa.boot.compatibility.AbstractSwitchableCompatibilityVerifier;
+import com.alipay.sofa.boot.compatibility.CompatibilityPredicate;
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.core.env.Environment;
+import org.springframework.mock.env.MockEnvironment;
+
+/**
+ * @author huzijie
+ * @version AbstractSwitchableCompatibilityVerifierTest.java, v 0.1 2023年08月07日 11:56 AM huzijie Exp $
+ */
+public class AbstractSwitchableCompatibilityVerifierTest {
+
+ private final MockEnvironment mockEnvironment = new MockEnvironment();
+
+ @Test
+ public void enableKey() {
+ mockEnvironment.setProperty("sofa.boot.compatibility-verifier.test.enabled", "true");
+ TestSwitchableCompatibilityVerifier verifier = new TestSwitchableCompatibilityVerifier(
+ mockEnvironment);
+ Assert.assertTrue(verifier.verify().isNotCompatible());
+ }
+
+ @Test
+ public void disableKey() {
+ mockEnvironment.setProperty("sofa.boot.compatibility-verifier.test.enabled", "false");
+ TestSwitchableCompatibilityVerifier verifier = new TestSwitchableCompatibilityVerifier(
+ mockEnvironment);
+ Assert.assertFalse(verifier.verify().isNotCompatible());
+ }
+
+ public static class TestSwitchableCompatibilityVerifier extends
+ AbstractSwitchableCompatibilityVerifier {
+
+ public TestSwitchableCompatibilityVerifier(Environment environment) {
+ super(environment);
+ }
+
+ @Override
+ public CompatibilityPredicate compatibilityPredicate() {
+ return () -> false;
+ }
+
+ @Override
+ public String errorDescription() {
+ return "fafa";
+ }
+
+ @Override
+ public String action() {
+ return "fafa";
+ }
+
+ @Override
+ public String enableKey() {
+ return "test";
+ }
+ }
+}
diff --git a/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/CompositeCompatibilityVerifierTest.java b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/CompositeCompatibilityVerifierTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..17694b06b8143bc0e295ef0615fcfd173919f9eb
--- /dev/null
+++ b/sofa-boot-project/sofa-boot/src/test/java/com/alipay/sofa/boot/test/compatibility/CompositeCompatibilityVerifierTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 com.alipay.sofa.boot.test.compatibility;
+
+import com.alipay.sofa.boot.compatibility.CompatibilityNotMetException;
+import com.alipay.sofa.boot.compatibility.CompatibilityVerifier;
+import com.alipay.sofa.boot.compatibility.CompositeCompatibilityVerifier;
+import com.alipay.sofa.boot.compatibility.VerificationResult;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author huzijie
+ * @version CompositeCompatibilityVerifierTest.java, v 0.1 2023年08月07日 12:00 PM huzijie Exp $
+ */
+public class CompositeCompatibilityVerifierTest {
+
+ @Test
+ public void empty() {
+ CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(
+ new ArrayList<>());
+ verifier.verifyCompatibilities();
+ }
+
+ @Test
+ public void pass() {
+ CompatibilityVerifier compatibilityVerifier = VerificationResult::compatible;
+ List verifiers = new ArrayList<>();
+ verifiers.add(compatibilityVerifier);
+ CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(verifiers);
+ verifier.verifyCompatibilities();
+ }
+
+ @Test
+ public void notPass() {
+ CompatibilityVerifier compatibilityVerifier = () -> VerificationResult.notCompatible("verify error", "do action");
+ List verifiers = new ArrayList<>();
+ verifiers.add(compatibilityVerifier);
+ CompositeCompatibilityVerifier verifier = new CompositeCompatibilityVerifier(verifiers);
+ try {
+ verifier.verifyCompatibilities();
+ Assert.fail();
+ } catch (CompatibilityNotMetException e) {
+ Assert.assertTrue(e.getMessage().contains("description: verify error"));
+ Assert.assertTrue(e.getMessage().contains("action: do action"));
+ }
+ }
+}
diff --git a/sofa-boot-project/sofaboot-dependencies/pom.xml b/sofa-boot-project/sofaboot-dependencies/pom.xml
index c54208700737192614279afd8bac33756235be58..e5b5e49041d8eef9f0f764fcb7a674e3d7a1d5a5 100644
--- a/sofa-boot-project/sofaboot-dependencies/pom.xml
+++ b/sofa-boot-project/sofaboot-dependencies/pom.xml
@@ -25,7 +25,7 @@
5.4.2
- 3.1.0
+ 3.1.3
5.8.3
@@ -38,13 +38,13 @@
1.7.32
1.2.69
3.28.0-GA
- 3.11.0
3.0
28.2-jre
2.11.3
2.7.7
2.9.10
1.0.2.Final
+ 3.22.2
3.6.3.Final
4.0.1
@@ -53,7 +53,7 @@
1.0.0
2.0.0
3.11.0
- 1.28.0
+ 1.53.0
1.17.0
@@ -469,6 +469,13 @@
+
+ com.google.protobuf
+ protobuf-bom
+ ${protobuf.version}
+ import
+ pom
+
com.alibaba
fastjson
@@ -484,11 +491,6 @@
guice-multibindings
${guice.version}
-
- com.google.protobuf
- protobuf-java
- ${protobuf-java.version}
-
org.javassist
javassist