I have a JPA application that has specified JTA transactions in persistence.xml. For whatever reason, I have found that when using JTA, you MUST specify a JNDI data source within persistence.xml as well. This is fine, unless you are trying to go integration testing outside a container and JNDI is not available.
My questions are:
a) is there anyway to inject a jdbc datasource into my JTA transaction manager?
b) if not, how do a handle a JNDI lookup during integration testing?
Edit: The error I get when firing up the integration test is:
Caused by: org.springframework.......DataSourceLookupFailureException:
Failed to look up JNDI DataSource with name 'java:comp/env/jdbc/myAppDataSource';
nested exception is javax.naming.NoInitialContextException: Need to specify
class name in environment or system property, or as an applet parameter,
or in an application resource file: java.naming.factory.initial
I didn't experiment this myself but it is doable with a standalone transaction manager and a XA compliant datasource. With Spring 2.5, most samples use JOTM and the JotmFactoryBean
and the XAPool. But these are non longer supported in Spring 3.0 and Atomikos appears to be the "replacement" (it provides a standalone transaction manager and a XA datasource). I've added some configuration samples below.
The alternative would be to run in-container tests (using an embedded Java EE container e.g. GlassFish v3 Embedded or an API like Cargo).
Resources
- Spring, JPA, and JTA with Hibernate and JOTM
- Using Spring with multiple databases Part 1 (check the whole serie)
- Replacing JOTM and XAPool with Atomikos (0.9.3) (another configuration example)
- Spring / JTA / JPA unit test : Rollback not working
- Guy Pardon: Transactional J2EE Apps with Spring
I ran into a similar problem myself. I'm using Spring with JPA and specifying my database as a JNDI name. Eventually I decided to just mock out JNDI using Mockito. This was pretty easy. Here is how I did it:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-config.xml"})
public class MyTestClass {
private static final InitialContextFactoryBuilder contextFactoryBuilder = mock(InitialContextFactoryBuilder.class);
private static final InitialContextFactory contextFactory = mock(InitialContextFactory.class);
private static final Context context = mock(Context.class);
private static final NameParser parser = mock(NameParser.class);
private static final Name dbName = mock(Name.class);
// This is the Datasource implementation from the H2 database
private static final JdbcDataSource temporaryDbForTesting = new JdbcDataSource();
@BeforeClass
public static void setupMockJndi() throws NamingException {
NamingManager.setInitialContextFactoryBuilder(contextFactoryBuilder);
when(contextFactoryBuilder.createInitialContextFactory(any(Hashtable.class))).thenReturn(contextFactory);
when(contextFactory.getInitialContext(any(Hashtable.class))).thenReturn(context);
when(context.getNameParser(any(String.class))).thenReturn(parser);
when(parser.parse("GatewayDbDataSource")).thenReturn(dbName);
when(context.lookup(dbName)).thenReturn(temporaryDbForTesting);
temporaryDbForTesting.setURL("jdbc:h2:~/test2");
temporaryDbForTesting.setUser("sa");
temporaryDbForTesting.setPassword("");
}
@Autowired
private org.zzz.DomainObject toTest;
@Test
public void testPasswordChecking() {
assertNotNull("There wasn't an object to test. Spring is broken!", toTest);
// ... assertions and such ...
}
}