Каков наилучший способ внедрить ложные зависимости Spring @Autowired из модульного теста? - PullRequest
16 голосов
/ 27 июля 2011
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's ReflectionTestUtils в тесте для вставки зависимостей
  2. Добавление конструктора вMyService
  3. Добавить методы установки в MyService
  4. Снизить видимость зависимостей для защищенных пакетов и установить поля напрямую

Что лучше и почему?

--- ОБНОВЛЕНИЕ ---

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

Ответы [ 3 ]

19 голосов
/ 27 июля 2011

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

3 голосов
/ 27 июля 2011

SpringConconfiguration может сделать это для вас.

Например, в контексте теста ниже «Локальные» классы являются ложными.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 - это класс 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 = _
2 голосов
/ 28 июля 2011

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

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

...