Каков наилучший способ инъецировать насмешливые 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 относительно неважна, так как все мы все кодируем интерфейсы, правильно?