Spring Boot - 在集成测试中通过 "new" 关键字创建的模拟有状态对象

SpringBoot - Mock stateful object created via "new" keyword in integration test

我有一个 SpringBoot 应用程序,它由一个 Controller 层和一个 Service 层组成。

MyController 可以通过 @Autowired 访问 MyService,而 MyService 有一个创建 MyClass 新实例的方法,该实例是从外部依赖。

import externaldependency.MyClass;

@Service
public class MyService {

    public void myMethod() {

        MyClass c = new MyClass();
        c.doStuff();
        c.doOtherStuff();
        c.doMoreStuff();

    }
}

我使用 new 创建实例,因为 MyClass 保持状态;它有几种方法可以在 myMethod 执行期间更改其状态,直到我得到所需的结果,因此我不应该自动装配它也不应该将它注入构造函数,因为那样会使用这个 [=41 的单个实例=] 每次调用 myMethod。我知道存在“原型”bean,但据我所知,即使我将 MyClass 声明为原型 bean 并通过 @Autowired 将其注入 MyService,该服务仍会使用执行期间 MyClass 的相同实例,所以最终我决定只使用 new.

最近我一直在尝试进行集成测试,调用我的 Controller 层,它又会调用我的 Service 层,后者又会创建 MyClass 的实例。问题是 MyClass 的许多方法之一在内部调用外部服务,这不应该是测试本身的一部分,所以我想模拟这个 class.

我知道模拟是通过依赖注入完成的,但在这种情况下我不能那样做。是否有替代方法来模拟 MyClass,或者这种设置根本不可能?如果没有,那么我如何重构我的代码以在这种特殊情况下进行模拟?

非常感谢。

我会回答我自己的问题。

由于 MyClass 保持状态,它不应自动连接到服务,也不应通过其构造函数注入,而是应根据需要创建新实例。然而,whan can 是一个创建这些实例的“工厂”:

@Component
class MyClassFactory {

    public MyClass getInstance() {
        return new MyClass();
    }

}

因此,服务变为:

@Service
public class MyService {

    @Autowired
    private MyClassFactory myClassFactory;

    public void myMethod() {

        // MyClass c = new MyClass();
        MyClass c = myClassFactory.getInstance();

        c.doStuff();
        c.doOtherStuff();
        c.doMoreStuff();

    }

}

实际上,使用工厂与只使用 new 是一样的;无论哪种方式,我都会得到一个新实例。好处来自测试;现在我可以模拟工厂 returns,因为工厂是 Spring 的应用程序上下文的一部分:

@SpringBootTest
public class MyTest {

    @MockBean
    private MyClass myClassMock;

    @MockBean
    private MyClassFactory myClassFactoryMock;

    @Test
    public void myTests() {

        // Make a mock of MyClass, replacing the return
        // values of its methods as needed.

        given(
            myClassMock.doStuff()
        ).willReturn(
            "Something useful for testing"
        );

        // Then make a mock of the factory, so that it returns
        // the mock of the class instead of a real instance.

        given(
            myClassFactoryMock.getInstance()
        ).willReturn(
            myClassMock
        );

        // Do the tests as normal.

    }

}

可能不是最优雅的解决方案,但至少解决了我当前的问题。