从单元测试中注入模拟Spring @Autowired依赖项的最佳方法是什么?

时间:2011-07-27 07:36:34

标签: java spring dependency-injection mocking autowired

import org.springframework.beans.factory.annotation.Autowired;

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

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

在测试这个类时,我基本上有四种注入模拟依赖项的方法:

  1. 在测试中使用Spring的ReflectionTestUtils来注入依赖项
  2. 向MyService添加构造函数
  3. 向MyService添加setter方法
  4. 放宽对包受保护的依赖性可见性并直接设置字段
  5. 哪个最好,为什么?

    ---更新---

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

3 个答案:

答案 0 :(得分:19)

使用ReflectionTestUtils或放置一个二传手。要么没事。添加构造函数可能会产生副作用(例如,不允许通过CGLIB进行子类化),仅仅为了测试而放宽可见性并不是一个好方法。

答案 1 :(得分:3)

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 = _

答案 2 :(得分:2)

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

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