Каков наилучший способ инъецировать насмешливые Spring @Autowired зависимости от unit test?

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

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

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

При тестировании этого класса у меня в основном есть четыре способа ввода ложных зависимостей:

  • Используйте Spring ReflectionTestUtils в тесте для ввода зависимостей
  • Добавить конструктор в MyService
  • Добавить методы настройки для MyService
  • Ослабьте видимость зависимостей для защиты пакетов и установите поля непосредственно

Какой из лучших и почему?

--- UPDATE ---

Я предполагаю, что я должен был быть более ясным - я говорю только о тестах стиля "unit", а не в тестах "интеграции" Spring, где зависимости могут быть связаны с использованием контекста Spring.

Ответы

Ответ 1

Используйте ReflectionTestUtils или поставьте сеттер. Либо все в порядке. Добавление конструкторов может иметь побочные эффекты (например, запретить подклассификацию CGLIB), и ослабление видимости только для тестирования не является хорошим подходом.

Ответ 2

Spring ContextConfiguration может сделать это за вас.

Например, в тестовом контексте ниже "Локальные" классы являются mocks. NotificationService - класс, который я хочу проверить.

Я использую компонентное сканирование, чтобы вывести mocks в контекст, но вы можете так же легко использовать объявления <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 - это класс Groovy с @Delegate.

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

Ответ 3

2) Используйте @Autowired инсталляцию конструктора (если это вариант 2, в противном случае сделайте опцию 5)

Правильные конструкторы, которые создают объект в правильном состоянии, являются более корректным объектно-ориентированным подходом, а потеря прокси-серверов cglib относительно неважна, так как все мы все кодируем интерфейсы, правильно?