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 lì 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.
+1, buona scoperta .. – Bozho
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
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. –