【问题标题】:SpringBoot - Mock stateful object created via "new" keyword in integration testSpring Boot - 在集成测试中通过“new”关键字创建的模拟状态对象
【发布时间】:2021-05-14 12:32:39
【问题描述】:

我有一个 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 的执行期间改变它的状态,直到我得到想要的结果,因此我不应该自动装配它,也不应该将它注入到构造函数中,因为每次调用都会使用这个类的单个实例myMethod。我知道存在“原型”bean,但据我所知,即使我将 MyClass 声明为原型 bean 并通过 @Autowired 将其注入 MyService,该服务仍将使用 @987654334 的相同实例@ 在执行期间,所以最终我决定只使用new

最近我一直在尝试做一个集成测试,调用我的控制器层,这反过来又会调用我的服务层,这又会创建一个MyClass 的实例。问题是MyClass 的众多方法之一在内部调用了一个外部服务,它不应该是测试本身的一部分,所以我想模拟这个类。

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

非常感谢。

【问题讨论】:

    标签: java spring-boot junit mocking integration-testing


    【解决方案1】:

    我会回答我自己的问题。

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

    @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 是一回事;无论哪种方式,我都会得到一个新实例。好处来自于测试;现在我可以模拟工厂返回的内容,因为工厂是 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.
    
        }
    
    }
    

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

    【讨论】:

      猜你喜欢
      • 2015-04-25
      • 2020-02-17
      • 2018-03-18
      • 2019-11-10
      • 2020-01-15
      • 2021-04-09
      • 2021-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多