【问题标题】:What is the best way to inject mocked Spring @Autowired dependencies from a unit test?从单元测试中注入模拟 Spring @Autowired 依赖项的最佳方法是什么?
【发布时间】:2011-10-14 00:46:37
【问题描述】:
import org.springframework.beans.factory.annotation.Autowired;

class MyService {
  @Autowired private DependencyOne dependencyOne;
  @Autowired private DependencyTwo dependencyTwo;

  public void doSomething(){
    //Does something with dependencies
  }
}

在测试这个类的时候,我基本上有四种方式注入mock依赖:

  1. 在测试中使用 Spring 的 ReflectionTestUtils 注入依赖项
  2. 向 MyService 添加构造函数
  3. 向 MyService 添加 setter 方法
  4. 放松对包保护的依赖可见性并直接设置字段

哪个最好,为什么?

--- 更新 ---

我想我应该更清楚一点 - 我只谈论“单元”样式测试,而不是可以使用 Spring 上下文连接依赖关系的 Spring“集成”样式测试。

【问题讨论】:

    标签: java spring dependency-injection mocking autowired


    【解决方案1】:

    使用ReflectionTestUtils 或放置一个setter。哪一个都好。添加构造函数可能会产生副作用(例如,不允许通过 CGLIB 进行子类化),并且仅仅为了测试而放松可见性并不是一个好方法。

    【讨论】:

    • 这通常也是我最终要做的 (ReflectionTestUtils)。在我看来,这是 Spring 中为数不多的主要弱点之一,缺乏对直接在上下文中轻松替换/模拟 bean 的支持。
    • “这是 Spring 为数不多的主要弱点之一,缺乏对直接将 bean 轻松替换/模拟到上下文中的支持” - 但他的单元测试中没有 Spring 上下文。他只是newMyService 类。
    • @matt b - 没关系 - 问题是关于设置依赖关系的可能性。
    • Setter 很丑,因为它们只被测试使用,而 RefelectionTestUtils 很丑,因为它跟不上字段名称重构。哪个不那么丑?
    • setters :) 因为它们是设置依赖项的正常方式。 Spring 使用字段反射来做到这一点,但它可能是使用 setter 反射(bean 属性)
    【解决方案2】:

    Spring 的 ContextConfiguration 可以为您做到这一点。

    例如,在下面的测试上下文中,“Local”类是模拟类。 NotificationService 是我要测试的课程。

    我正在使用组件扫描将模拟带入上下文,但您也可以轻松地使用 <bean> 声明。注意 use-default-filters="false" 的使用。

    <context:component-scan base-package="com.foo.config" use-default-filters="false">
        <context:include-filter type="assignable" 
            expression="com.foo.LocalNotificationConfig"/>
    </context:component-scan>
    
    <context:component-scan base-package="com.foo.services.notification"
            use-default-filters="false">
        <context:include-filter type="assignable"
            expression="com.foo.services.notification.DelegatingTemplateService"/>
        <context:include-filter type="assignable"
            expression="com.foo.services.notification.NotificationService"/>
    </context:component-scan>
    
    <context:component-scan base-package="com.foo.domain"/>
    

    DelegatingTemplateService 是一个带有@Delegate 的 Groovy 类。

    class DelegatingTemplateService {
      @Delegate
      TemplateService delegate
    }
    

    在测试类中,我使用测试上下文并注入服务进行测试。在设置中,我设置了 DelegatingTemplateService 的委托:

    @RunWith(classOf[SpringJUnit4ClassRunner])
    @ContextConfiguration(Array("/spring-test-context.xml"))
    class TestNotificationService extends JUnitSuite {
      @Autowired var notificationService: NotificationService = _
      @Autowired var templateService: DelegatingTemplateService = _
    
      @Before
      def setUp {
        templateService.delegate = /* Your dynamic mock here */
      }  
    

    在服务中,@Autowired 字段是私有的:

    @Component("notificationService")
    class NotificationServiceImpl extends NotificationService {
      @Autowired private var domainManager: DomainManager = _
      @Autowired private var templateService: TemplateService = _
      @Autowired private var notificationConfig: NotificationConfig = _
    

    【讨论】:

    • 是的,您可以交换实现以进行测试和生产。但我想到了在运行时使用 Mockito 之类的东西生成的模拟,因此我的答案是。
    • 抓住了 - 使用 Groovy @Delegate 怎么样?请参阅我的更新答案。
    【解决方案3】:

    2) 使用@Autowired 构造函数注入(如果那是选项 2;否则,使用选项 5)

    在有效状态下创建对象的正确构造函数是一种更正确的面向对象方法,并且丢失 cglib 代理相对不重要,因为无论如何我们都将代码编写为接口,对吧??

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-18
      • 1970-01-01
      相关资源
      最近更新 更多