2010-11-09 5 views
5

Ich stieß auf ein Problem, das nur mit meinem grundlegenden Mangel an Verständnis der Spring IoC Container-Einrichtungen und Kontext-Setup erklärt werden kann, so würde ich um Klärung bitten.Spring JUnit4 manual-/auto-wiring dilemma

nur als Referenz, eine Anwendung, die ich die folgenden Stapel von Technologien am maintaing:

  • Java 1.6
  • Frühling 2.5.6
  • Richfaces 3.3.1-GA UI
  • Spring-Framework wird für Bean Management mit Spring verwendet JDBC Modul für DAO Unterstützung
  • Maven wird als Build Manager verwendet
  • JUnit 4.4 ist nicht w als Testmotor eingeführt

Ich bin rückwirkend (sic!) Schreiben JUnit-Tests für die Anwendung und was überrascht mich ist, dass ich, ohne auf eine Bohne in einer Testklasse zu injizieren, durch die Verwendung Setter Injektion nicht in der Lage @Autowire-Notation.

Lassen Sie mich einrichten, ein Beispiel und die begleitenden Konfigurationsdateien einrichten.

Die Testklasse TypeTest ist wirklich einfach:

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

ist sein Kontext in TestStackOverflowExample-context.xml definiert:

<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 in Classpath und enthält auf ly db-spezifische Daten, die für die Datenquelle benötigt werden.

Dies funktioniert wie ein Charme, aber meine Frage ist - warum es nicht funktioniert, wenn ich manuell Draht Bohnen versuchen und Setter-Injektion durchführt, wie 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... 
    } 
} 

Was bin ich hier? Welcher Teil der Konfiguration ist hier falsch? Wenn ich versuche, Beans manuell über Setter zu injizieren, schlägt der Test fehl, weil dieser Teil

result = type.findAlltTypes(); 

in Runtime als null aufgelöst wird. Ich habe natürlich das Spring-Referenzhandbuch konsultiert und verschiedene Kombinationen der XML-Konfiguration ausprobiert; alles, was ich schließen konnte, ist, dass Spring nicht in der Lage war, Beans zu injizieren, weil es die Spring Test Context-Referenz nicht richtig dereferenziert, aber bei Verwendung von @Autowired geschieht dies "automatisch" und ich kann wirklich nicht sehen, warum das so ist, weil JavaDoc sowohl Autowired Annotation und seine PostProcessor Klasse erwähnt dies nicht.

Ebenfalls wert ist die Tatsache, dass @Autowired in der Anwendung nur hier verwendet wird. Anderswo wird nur manuelle Verdrahtung durchgeführt, so bringt dies auch Frage hervor - warum funktioniert es dort und nicht hier, in meinem Test? Welchen Teil der DI-Konfiguration fehlt mir? Wie erhält @Autowired Bezug auf Spring Context?

EDIT: Ich habe dies auch versucht, aber mit gleichen Ergebnissen:

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

Jede andere Ideen, vielleicht?

EDIT2: Ich habe einen Weg gefunden, ohne eigene TestContextListener oder BeanPostProcessor schreiben zu schreiben. Es ist überraschend einfach und es stellt sich heraus, dass ich auf dem richtigen Weg mit meinem zuletzt war:

1) Constructor-basierten Kontext Lösung:

@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) Durch die Implementierung ApplicationContextAware Schnittstelle:

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

Beide dieser Ansätze ordnungsgemäß Beans Bohnen.

Antwort

5

Wenn Sie einen Blick auf die Quelle der org.springframework.test.context.support.DependencyInjectionTestExecutionListener nehmen, werden Sie die folgende Methode sehen (formatiert und kommentiert für Klarheit):

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

So das Testobjekt ist eine Bean ohne Auto-Verkabelung. Jedoch, @AutoWired, @Resource usw., verwenden Sie nicht den Autowiring-Mechanismus, sie verwenden BeanPostProcessor. Und so werden die Abhängigkeiten genau dann eingefügt, wenn die Annotationen verwendet werden (oder wenn Sie eine andere BeanPostProcessor registrieren, die das tut).

(Der obige Code von Spring 3.0.x ist, aber ich wette, es ist das gleiche in 2.5.x war)

+0

+1, gut finden .. – Bozho

+0

So sind uns sicher dort zu schließen, ist effektiv keine Möglichkeit, Testbohnen manuell verdrahten? Ich möchte auf Anmerkungen verzichten, wenn es überhaupt möglich ist. – quantum

+0

Natürlich kann es gemacht werden. Im Frühling kann fast alles gemacht werden. Sie müssten Ihre eigenen a) BeanPostProcessor oder b) TestExecutionListener schreiben, suchen Sie nach der Bean für Ihre Testklasse und verbinden Sie sie mit der AutowireCapableBeanFactory. –