Spring JUnit4 дилемма ручного / автоматического подключения - PullRequest
5 голосов
/ 09 ноября 2010

Я столкнулся с проблемой, которую можно объяснить только моим фундаментальным отсутствием понимания возможностей контейнера IoC Spring и настройки контекста, поэтому я бы попросил разъяснений по этому поводу.

Просто для справки, приложение, которое я поддерживаю, имеет следующий набор технологий:

  • Java 1.6
  • Пружина 2.5.6
  • RichFaces 3.3.1-GA UI
  • Spring Framework используется для управления компонентами с модулем Spring JDBC, используемым для поддержки DAO
  • Maven используется в качестве менеджера сборки
  • JUnit 4.4 теперь представлен как тестовый движок

Я задним числом (sic!) Пишу тесты JUnit для приложения, и меня удивило то, что я не смог внедрить bean-компонент в тестовый класс, используя инъекцию сеттера без обращения к нотации @Autowire.

Позвольте мне предоставить пример настройки и сопровождающие файлы конфигурации.

Тестовый класс TypeTest действительно прост:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {

    @Autowired
    private IType type;

    @Test
    public void testFindAllTypes() {
        List<Type> result;

        try {
            result = type.findAlltTypes();
            assertNotNull(result);
        } catch (Exception e) {
            e.printStackTrace();
            fail("Exception caught with " + e.getMessage());
        }
    }
}

Его контекст определен в TestStackOverflowExample-context.xml:

<context:property-placeholder location="classpath:testContext.properties" />
<context:annotation-config />
<tx:annotation-driven />

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${db.connection.driver.class}" />
    <property name="url" value="${db.connection.url}" />
    <property name="username" value="${db.connection.username}" />
    <property name="password" value="${db.connection.password}" />
</bean>

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="beanDAO" class="com.example.BeanDAOImpl">
    <property name="ds" ref="dataSource"></property>
    <property name="beanDAOTwo" ref="beanDAOTwo"></property>
</bean>

<bean id="beanDAOTwo" class="com.example.BeanDAOTwoImpl">
    <property name="ds" ref="dataSource"></property>
</bean>

<bean id="type" class="com.example.TypeImpl">
    <property name="beanDAO" ref="beanDAO"></property>
</bean>

TestContext.properties находится в classpath и содержит только специфичные для БД данные, необходимые для источника данных.

Это работает как талисман, но мой вопрос - почему это не работает, когда я пытаюсь вручную подключить бины и выполнить установку сеттера, как в:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {

    private IType type;

    public IType getType () {
        return type;
    }

    public void setType(IType type) {
        this.type= type;
    }

    @Test
    public void testFindAllTypes(){
    //snip, snip...
    }
}

Что мне здесь не хватает? Какая часть конфигурации здесь не так? Когда я пытаюсь вручную ввести bean-компоненты с помощью сеттеров, тест завершается неудачно, потому что эта часть

result = type.findAlltTypes();

разрешается как ноль во время выполнения. Я, конечно, обратился к справочному руководству Spring и попробовал различные комбинации конфигурации XML; все, что я могу сделать, - это то, что Spring не смог внедрить bean-компоненты, потому что он почему-то не может правильно разыменовать ссылку на контекст Spring Test Context, но с помощью @Autowired это происходит «автоматически», и я действительно не могу понять, почему это происходит, потому что JavaDoc обоих Autowired аннотация и класс PostProcessor этого не упоминают.

Также стоит добавить, что @Autowired используется в приложении только здесь. В других местах выполняется только ручное подключение, поэтому возникает вопрос - почему он работает там , а не здесь , в моем тесте? Какую часть конфигурации DI я пропускаю? Как @Autowired получить ссылку на Spring Context?

EDIT: Я тоже пробовал это, но с такими же результатами:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{

    private IType type;

    private ApplicationContext ctx;

    public TypeTest(){
              super();
              ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
              ctx.getBean("type");
    }

    public IType getType () {
        return type;
    }

    public void setType(IType type) {
        this.type= type;
    }

    @Test
    public void testFindAllTypes(){
    //snip, snip...
    }
}

Возможно, есть еще идеи?

EDIT2: Я нашел способ, не прибегая к написанию собственных TestContextListener или BeanPostProcessor. Это удивительно просто, и оказывается, что я был на правильном пути с моим последним редактированием:

1) Разрешение контекста на основе конструктора:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest{

    private IType type;

    private ApplicationContext ctx;

    public TypeTest(){
         super();
         ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
         type = ctx.getBean("type");
    }

    public IType getType () {
        return type;
    }

    public void setType(IType type) {
        this.type= type;
    }

    @Test
    public void testFindAllTypes(){
    //snip, snip...
    }
}

2) Реализация интерфейса ApplicationContextAware:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{

    private IType type;
    private ApplicationContext ctx;

    public IType getType () {
        return type;
    }

    public void setType(IType type) {
        this.type= type;
    }

@Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
    this.ctx = ctx;
    type = (Type) ctx.getBean("type");
}

    @Test
    public void testFindAllTypes(){
    //snip, snip...
    }
}

В обоих этих подходах правильно реализованы компоненты.

1 Ответ

5 голосов
/ 09 ноября 2010

Если вы посмотрите на источник org.springframework.test.context.support.DependencyInjectionTestExecutionListener, вы увидите следующий метод (отформатированный и прокомментированный для ясности):

protected void injectDependencies(final TestContext testContext)
throws Exception {
    Object bean = testContext.getTestInstance();
    AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext()
            .getAutowireCapableBeanFactory();
    beanFactory.autowireBeanProperties(bean, 

            AutowireCapableBeanFactory.AUTOWIRE_NO,
            // no autowiring!!!!!!!!

            false
        );

    beanFactory.initializeBean(bean, testContext.getTestClass().getName());
    // but here, bean post processors are run

    testContext.removeAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE);
}

Таким образом, тестовый объект - это бин без автопроводки,Однако @AutoWired, @Resource и т. Д. Не используют механизм автопроводки, они используют BeanPostProcessor.И поэтому зависимости вводятся тогда и только тогда, когда используются аннотации (или если вы регистрируете какой-то другой BeanPostProcessor, который это делает).

(приведенный выше код взят из Spring 3.0.x, но я готов поспорить, чтобыло то же самое в 2.5.x)

...