Browse Source

Reject attempt to use @⁠MockitoSpyBean with a scoped proxy

Prior to this commit, an attempt to use @⁠MockitoSpyBean to spy on a
scoped proxy configured with
@⁠Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) resulted in an
exception thrown by Mockito when the spy was stubbed. The exception
message stated "Failed to unwrap proxied object" but did not provide
any further insight or context for the user.

The reason is that ScopedProxyFactoryBean is used to create such a
scoped proxy, which uses a SimpleBeanTargetSource, which is not a
static TargetSource. Consequently,
SpringMockResolver.getUltimateTargetObject(Object) is not able to
unwrap the proxy.

In order to improve diagnostics for users, this commit eagerly detects
an attempt to spy on a scoped proxy and throws an exception with a
meaningful message. The following is an example, trimmed stack trace
from the test suite.

org.springframework.beans.factory.BeanCreationException:
  Error creating bean with name 'myScopedProxy': Post-processing of
  FactoryBean's singleton object failed
  ...
Caused by: java.lang.IllegalStateException:
  @⁠MockitoSpyBean cannot be applied to bean 'myScopedProxy', because
  it is a Spring AOP proxy with a non-static TargetSource. Perhaps you
  have attempted to spy on a scoped proxy, which is not supported.
    at ...MockitoSpyBeanOverrideHandler.createSpy(MockitoSpyBeanOverrideHandler.java:78)

Closes gh-35722
pull/35768/head
Sam Brannen 2 months ago
parent
commit
c0c94d5d86
  1. 2
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideHandler.java
  2. 21
      spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/SpringMockResolver.java
  3. 67
      spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanConfigurationErrorTests.java

2
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanOverrideHandler.java

@ -69,9 +69,11 @@ class MockitoSpyBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler { @@ -69,9 +69,11 @@ class MockitoSpyBeanOverrideHandler extends AbstractMockitoBeanOverrideHandler {
}
private Object createSpy(String name, Object instance) {
SpringMockResolver.rejectUnsupportedSpyTarget(name, instance);
Class<?> resolvedTypeToOverride = getBeanType().resolve();
Assert.notNull(resolvedTypeToOverride, "Failed to resolve type to override");
Assert.isInstanceOf(resolvedTypeToOverride, instance);
if (Mockito.mockingDetails(instance).isSpy()) {
return instance;
}

21
spring-test/src/main/java/org/springframework/test/context/bean/override/mockito/SpringMockResolver.java

@ -79,4 +79,25 @@ public class SpringMockResolver implements MockResolver { @@ -79,4 +79,25 @@ public class SpringMockResolver implements MockResolver {
return candidate;
}
/**
* Reject the supplied bean if it is not a supported candidate to spy on.
* <p>Specifically, this method ensures that the bean is not a Spring AOP proxy
* with a non-static {@link TargetSource}.
* @param beanName the name of the bean to spy on
* @param bean the bean to spy on
* @since 7.0
* @see #getUltimateTargetObject(Object)
*/
static void rejectUnsupportedSpyTarget(String beanName, Object bean) throws IllegalStateException {
if (SPRING_AOP_PRESENT) {
if (AopUtils.isAopProxy(bean) && bean instanceof Advised advised &&
!advised.getTargetSource().isStatic()) {
throw new IllegalStateException("""
@MockitoSpyBean cannot be applied to bean '%s', because it is a Spring AOP proxy \
with a non-static TargetSource. Perhaps you have attempted to spy on a scoped proxy, \
which is not supported.""".formatted(beanName));
}
}
}
}

67
spring-test/src/test/java/org/springframework/test/context/bean/override/mockito/MockitoSpyBeanConfigurationErrorTests.java

@ -20,15 +20,22 @@ import java.util.List; @@ -20,15 +20,22 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.test.context.bean.override.BeanOverrideContextCustomizerTestUtils;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link MockitoSpyBean @MockitoSpyBean}.
*
* @author Stephane Nicoll
* @author Sam Brannen
*/
class MockitoSpyBeanConfigurationErrorTests {
@ -73,6 +80,39 @@ class MockitoSpyBeanConfigurationErrorTests { @@ -73,6 +80,39 @@ class MockitoSpyBeanConfigurationErrorTests {
List.of("bean1", "bean2"));
}
@Test // gh-35722
void mockitoSpyBeanCannotSpyOnScopedProxy() {
var context = new AnnotationConfigApplicationContext();
context.register(MyScopedProxy.class);
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(ScopedProxyTestCase.class, context);
context.refresh();
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> context.getBean(MyScopedProxy.class))
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessage("""
@MockitoSpyBean cannot be applied to bean 'myScopedProxy', because it is a \
Spring AOP proxy with a non-static TargetSource. Perhaps you have attempted \
to spy on a scoped proxy, which is not supported.""");
}
@Test // gh-35722
void mockitoSpyBeanCannotSpyOnSelfInjectionScopedProxy() {
var context = new AnnotationConfigApplicationContext();
context.register(MySelfInjectionScopedProxy.class);
BeanOverrideContextCustomizerTestUtils.customizeApplicationContext(SelfInjectionScopedProxyTestCase.class, context);
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessage("""
@MockitoSpyBean cannot be applied to bean 'mySelfInjectionScopedProxy', because it \
is a Spring AOP proxy with a non-static TargetSource. Perhaps you have attempted \
to spy on a scoped proxy, which is not supported.""");
}
static class ByTypeSingleLookup {
@ -88,4 +128,31 @@ class MockitoSpyBeanConfigurationErrorTests { @@ -88,4 +128,31 @@ class MockitoSpyBeanConfigurationErrorTests {
}
static class ScopedProxyTestCase {
@MockitoSpyBean
MyScopedProxy myScopedProxy;
}
static class SelfInjectionScopedProxyTestCase {
@MockitoSpyBean
MySelfInjectionScopedProxy mySelfInjectionScopedProxy;
}
@Component("myScopedProxy")
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
static class MyScopedProxy {
}
@Component("mySelfInjectionScopedProxy")
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
static class MySelfInjectionScopedProxy {
MySelfInjectionScopedProxy(MySelfInjectionScopedProxy self) {
}
}
}

Loading…
Cancel
Save