2010-11-09 16 views
5

Me encontré con un problema que solo puede explicarse por mi falta fundamental de comprensión de las instalaciones de contenedor IoC de Spring y la configuración de contexto, por lo que pediría una aclaración con respecto a esto.Primavera JUnit4 dilema de manual/auto-cableado

Sólo como referencia, una aplicación que estoy maintaing tiene la siguiente pila de tecnologías:

  • Java 1.6
  • primavera 2.5.6
  • RichFaces 3.3.1 GA-IU
  • marco de Primavera se utiliza para la gestión de beans con módulo Spring JDBC utilizado para soporte DAO
  • Maven se utiliza como administrador de compilación
  • JUnit 4.4 no es w introducido como motor de prueba

Soy retroactivamente (sic!) las pruebas de escritura JUnit para la aplicación y lo sorprendió mí es que yo no era capaz de inyectar un grano en una clase de prueba mediante inyección de setter sin recurrir a @Autowire notación.

Permítame proporcionarle la configuración de un ejemplo y los archivos de configuración que lo acompañan.

La clase de prueba TypeTest es muy 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()); 
     } 
    } 
} 

Su contexto se define en 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 la ruta de clase y contiene en datos específicos de db ly necesarios para el origen de datos.

Esto funciona como un encanto, pero mi pregunta es - ¿por qué no funciona cuando intento habas manual de alambre y llevar a cabo la inyección de la moda como en:

@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é me estoy perdiendo aquí? ¿Qué parte de la configuración está mal aquí? Cuando intento de inyectar manualmente los granos a través de los emisores, prueba falla porque esta parte

result = type.findAlltTypes(); 

se resuelve como nulo en tiempo de ejecución. Por supuesto, consulté el manual de referencia de Spring y probé varias combinaciones de configuración XML; todo lo que pude concluir es que Spring no pudo inyectar frijoles porque de alguna manera no eliminó correctamente la referencia de Spring Test Context pero al usar @Autowired esto sucede "automágicamente" y realmente no puedo ver por qué es que JavaDoc tanto de la anotación Autowired y su clase PostProcessor no menciona esto.

También vale la pena agregar es el hecho de que @Autowired se utiliza en la aplicación solamente aquí. En otros lugares solo se realiza el cableado manual, por lo que también surge una pregunta: ¿por qué está funcionando allí y no aquí, en mi prueba? ¿Qué parte de la configuración DI me falta? ¿Cómo obtiene @Autowired una referencia de Spring Context?

EDIT: También he probado esto, pero con el mismo resultado:

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

alguna otra idea, tal vez?

EDIT2: He encontrado una manera sin recurrir a la escritura propia TestContextListener o BeanPostProcessor. Es sorprendentemente simple y resulta que yo estaba en el camino correcto con mi última edición:

1) contexto basado en Constructor resolver:

@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) Mediante la implementación de la interfaz 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... 
    } 
} 

Ambos enfoques se aproximan a los beans.

Respuesta

5

Si se echa un vistazo a la fuente de org.springframework.test.context.support.DependencyInjectionTestExecutionListener, verá el siguiente método (con formato y comentado por claridad):

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

Por lo tanto el objeto de prueba es un grano sin auto-cableado. Sin embargo, @AutoWired, @Resource etc., no use el mecanismo de autoenlace, usan BeanPostProcessor. Y entonces las dependencias se inyectan si y solo si se usan las anotaciones (o si registra alguna otra BeanPostProcessor que lo haga).

(El código anterior es de resorte 3.0.x, pero apuesto a que era la misma en 2.5.x)

+0

1, buen hallazgo .. – Bozho

+0

nosotros también seguro concluir en realidad no habrá manera de ¿Alimentar manualmente los granos de prueba? Me gustaría omitir el uso de anotaciones si es posible. – quantum

+0

Por supuesto que se puede hacer. En primavera, casi todo se puede hacer. Debería escribir su propio a) BeanPostProcessor o b) TestExecutionListener, busque el bean para su clase de prueba y conéctelo utilizando AutowireCapableBeanFactory. –