2010-11-09 5 views
5

Mi sono imbattuto in un problema che può essere spiegato solo con la mia fondamentale mancanza di comprensione delle strutture del contenitore IoC di Spring e dell'impostazione del contesto, quindi vorrei chiedere un chiarimento in merito.Spring JUnit4 dilemma di cablaggio manuale/automatico

Solo per riferimento, un'applicazione che sto mantenendo ha il seguente stack di tecnologie:

  • Java 1.6
  • Primavera 2.5.6
  • RichFaces 3.3.1-GA UI
  • framework Spring viene utilizzato per la gestione dei bean con il modulo Spring JDBC utilizzato per il supporto DAO
  • Maven viene utilizzato come gestore build
  • JUnit 4.4 è no w introdotto come motore di prova

Sono retroattivamente (sic!) prove di scrittura JUnit per l'applicazione e ciò che sorpreso me è che non ero in grado di iniettare un fagiolo in una classe di test utilizzando iniezione setter senza ricorrere alla Notazione @Autowire.

Consentitemi di fornire un esempio e file di configurazione di accompagnamento.

La classe di test TypeTest è davvero semplice:

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

suo contesto è definita in 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 è nel classpath e contiene su i dati db-specifici necessari per l'origine dati.

Questo funziona come un fascino, ma la mia domanda è - perché non funziona quando cerco di fagioli manualmente filo ed eseguire l'iniezione setter come in:

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

Che cosa mi manca qui? Quale parte della configurazione è sbagliata qui? Quando provo a iniettare manualmente fagioli via setter, test non riesce perché questa parte

result = type.findAlltTypes(); 

si risolve come nullo in fase di esecuzione. Ho, ovviamente, consultato il manuale di riferimento Spring e ho provato varie combinazioni di configurazione XML; tutto quello che potevo concludere è che Spring non è stato in grado di iniettare i fagioli perché in qualche modo non riesce a dereferenziare correttamente il riferimento al contesto di test di prova ma usando @Autowired questo accade "automaticamente" e non vedo perché è JavaDoc di entrambe le annotazioni Autowired e la sua classe PostProcessor non lo menziona.

Vale anche la pena aggiungere che @Autowired viene utilizzato solo qui nell'applicazione. Altrove viene eseguito solo il cablaggio manuale, quindi questo pone anche una domanda: perché funziona e non qui nel, nel mio test? Quale parte della configurazione DI mi manca? In che modo @Autowired ottiene il riferimento di Spring Context?

EDIT: Inoltre ho provato questo, ma con gli stessi risultati:

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

altre idee, forse?

EDIT2: Ho trovato un modo senza ricorrere a scrivere proprio TestContextListener o BeanPostProcessor. E 'sorprendentemente semplice e si scopre che ero sulla strada giusta con il mio ultima modifica:

1) contesto di costruzione a base di risolvere:

@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) Con l'implementazione di un'interfaccia 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... 
    } 
} 

Entrambi questi approcci hanno correttamente istanziato i fagioli.

risposta

5

Se si dà un'occhiata alla fonte di org.springframework.test.context.support.DependencyInjectionTestExecutionListener, si vedrà il seguente metodo (formattato e commentato per chiarezza):

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

Così l'oggetto di prova è un fagiolo, senza auto-cablaggio. Tuttavia, @AutoWired, @Resource ecc., Non utilizzare il meccanismo di autowiring, utilizzano BeanPostProcessor. E così le dipendenze vengono iniettate se e solo se vengono utilizzate le annotazioni (o se si registra qualche altro BeanPostProcessor che lo fa).

(Il codice di cui sopra è da 3.0.x primavera, ma scommetto che era lo stesso in 2.5.x)

+0

+1, buona scoperta .. – Bozho

+0

Così siamo sicuri da concludere non v'è alcun modo per efficace collegare manualmente i fagioli test? Vorrei omettere di utilizzare le annotazioni se possibile. – quantum

+0

Ovviamente si può fare. In primavera, quasi tutto può essere fatto. Dovresti scrivere il tuo a) BeanPostProcessor o b) TestExecutionListener, cercare il bean per la tua classe di test e collegarlo usando AutowireCapableBeanFactory. –