2010-11-09 41 views
5

J'ai rencontré un problème qui ne peut s'expliquer que par mon manque fondamental de compréhension des installations du conteneur IoC de Spring et de la configuration du contexte. Je vous demanderai donc des éclaircissements à ce sujet.Spring JUnit4 manuel/câblage auto dilemme

Juste pour référence, une application que je suis maintaing a la pile suivante de technologies:

  • Java 1.6
  • Spring 2.5.6
  • RichFaces 3.3.1 GA-UI
  • framework Spring est utilisé pour la gestion des beans avec le module Spring JDBC utilisé pour le support DAO
  • Maven est utilisé comme gestionnaire de build
  • JUnit 4.4 est non w présenté comme moteur d'essai

Je suis avec effet rétroactif (sic!) des tests JUnit d'écriture pour l'application et ce qui m'a surpris est que je n'ai pas pu injecter un haricot dans une classe de test en utilisant l'injection setter sans avoir recours à @Autowire notation. Permettez-moi de fournir un exemple et des fichiers de configuration qui l'accompagnent.

La classe de test TypeTest est très simple:

@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()); 
     } 
    } 
} 

Son contexte est défini dans 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 est en classpath et contient en Les données spécifiques à DB sont nécessaires pour la source de données.

Cela fonctionne comme un charme, mais ma question est - pourquoi ça ne marche pas lorsque je tente de haricots fil manuellement et effectuer l'injection setter comme dans:

@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... 
    } 
} 

Qu'est-ce que je manque ici? Quelle partie de la configuration est fausse ici? Lorsque je tente d'injecter manuellement les haricots via setters, test échoue parce que cette partie

result = type.findAlltTypes(); 

est résolu comme nulle dans l'exécution. J'ai, bien sûr, consulté le manuel de référence de Spring et essayé différentes combinaisons de configuration XML; tout ce que je pourrais conclure est que Spring était incapable d'injecter des beans parce qu'il ne parvient pas à déréférencer correctement la référence Spring Test Context mais en utilisant @Autowired cela arrive "automagiquement" et je ne vois vraiment pas pourquoi est-ce parce que JavaDoc des annotations Autowired sa classe PostProcessor ne le mentionne pas.

Il est également intéressant d'ajouter que le @Autowired est utilisé ici uniquement dans les applications. Ailleurs, seul le câblage manuel est effectué, donc cela soulève également la question - pourquoi est-ce que cela fonctionne et pas ici, dans mon test? Quelle est la partie de la configuration DI qui me manque? Comment @Autowired obtient une référence de Spring Context?

EDIT: J'ai aussi essayé cela, mais avec les mêmes résultats:

@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... 
    } 
} 

D'autres idées, peut-être? J'ai trouvé un moyen sans avoir recours à l'écriture TestContextListener ou BeanPostProcessor. Il est étonnamment simple et il se trouve que je suis sur la bonne voie avec ma dernière modification:

1) contexte basé sur Constructor résolution:

@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) En mettant en œuvre l'interface 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... 
    } 
} 

Ces deux approches approchent correctement les beans.

Répondre

5

Si vous jetez un oeil à la source de org.springframework.test.context.support.DependencyInjectionTestExecutionListener, vous verrez la méthode suivante (format et commenté par souci de clarté):

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); 
} 

donc l'objet de test est un haricot sans auto-câblage. Cependant, @AutoWired, @Resource etc, n'utilisez pas le mécanisme d'autowiring, ils utilisent BeanPostProcessor. Et ainsi les dépendances sont injectées si et seulement si les annotations sont utilisées (ou si vous enregistrez un autre BeanPostProcessor qui le fait).

(Le code ci-dessus est du printemps 3.0.x, mais je parie que c'était la même dans 2.5.x)

+0

+1, une bonne trouvaille .. – Bozho

+0

Alors sommes-nous sûr de conclure il n'y a effectivement aucun moyen de fil manuellement des haricots de test? Je voudrais omettre d'utiliser des annotations si possible. – quantum

+0

Bien sûr, cela peut être fait. Au printemps, presque tout peut être fait. Vous devez écrire votre propre a) BeanPostProcessor ou b) TestExecutionListener, recherchez le bean pour votre classe de test et connectez-le en utilisant AutowireCapableBeanFactory. –