@SpyBean 有没有办法根据 Spring 启动测试中的接口类型为所有类型创建间谍?

Is there a way for @SpyBean to create spies for all types based on the interface type in a Spring Boot test?

我有一个 Spring 引导应用程序,我想在其中确保装饰器列表已被验证以执行。这些装饰器都从相同的抽象 class 扩展而来,后者又从相同的接口扩展,并且它们作为装饰器列表自动装配到服务 class 中。我原以为在测试的 class 级别提供 @SpyBean(MyDecorator.class) 就可以解决问题,但我收到错误消息,指出装饰器不是间谍。看起来 MockitoPostProcessor class 期望我们在注释中提供单独的具体 classes @SpyBean(classes = {decorator1.class,decorator2.class})。我尝试了后者,它起作用了。

但是,我遇到的问题是每次我们创建一个新的装饰器时我们都必须添加到这个列表中,这并不理想。这就是为什么我认为也检查接口类型是有意义的。如果有更好的方法,或者我错过了什么,请告诉我。我想到的一个想法是定义我自己的 post 处理器,以将来自已定义类型的任何 bean 包装在 mockito 间谍中,但我想先在这里检查一下。这是 classes 的框架定义,可帮助您理解我的困境。

MyDecorator.java

public interface MyDecorator{
    public void decorate(SomeObject obj);
}

AbstractDecorator.java

public class AbstractDecorator implements MyDecorator{
    //common decorator logic
}

Decorator1.java

@Component
public class Decorator1 extends AbstractDecorator{
    public void decorate(SomeObject obj){
        //decoration logic
    }
}

Decorator2.java

@Component
public class Decorator2 extends AbstractDecorator{
    public void decorate(SomeObject obj){
        //decoration logic
    }
}

DecorationService.java

@Service
public class DecorationService implements Service{

    @Autowired
    private List<MyDecorator> decoratorList;


    public void processDecorators(){
        //go through list of decorators and process some object
    }
}

DecoratorServiceTest.java

@Runwith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
//@SpyBean(MyDecorator.class) //<-- This doesn't wrap the classes in a spy and errors out
@SpyBean(classes = {Decorator1.class, Decorator2.class}) //<-- This works
public class DecoratorServiceTest{
    @Autowired
    private List<MyDecorator> decoratorList;

    @Test
    public void testProcessDecorator(){
        //verify that each decorator was processed
    }    
}

我发布了 spring 引导 github 问题 here。希望我们要么看到它的改进,要么得到关于为什么以这种方式设计它的解释。

我有一个解决方法,我正在使用它,我创建了一个 class 来实现 Spring 的 BeanPostProcessor 接口,并且我覆盖了 postProcessAfterInitialization 方法,然后检查 class 是否符合我的预期,然后将其包装在模拟间谍中。此外,您需要定义 spring bean。

这是我创建的 class 的片段。

public class SpyBeanPostProcessor<T> implements BeanPostProcessor{

    /**
     * The class type to spy on.
     */
    private Class<T> typeToSpy;

    /**
     * Construct a SpyBeanPostProcessor with a class type to wrap
     * as a {@link org.mockito.Spy}
     * @param typeToSpy The class type to spy on.
     */
    public SpyBeanPostProcessor(Class<T> typeToSpy) {
        this.typeToSpy = typeToSpy;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (typeToSpy.isAssignableFrom(bean.getClass())){
            return Mockito.spy(bean);
        }else{
            return bean;
        }
    }
}

我还需要创建一个新的 spring bean 来加载 BeanPostProcessor,如下所示。

    @Bean
    public static SpyBeanPostProcessor decoratorSpyBeanPostProcessor(){
        return new SpyBeanPostProcessor(MyDecorator.class);
    }