Hi quick question about injecting an alternative data-source during integration testing of an (EJB 3.0) EJB through its raw POJO API using junit.
I have been converting raw POJO services to EJB3 session beans. To do so has really just meant annotating the POJOs directly. The services are also accompanied by existing junit integration tests (that check the result of methods that query a real test database).
Several of these services require a direct java.sql.Connection and so I intend to configure this through an injected DataSource. The intention of this is so that I can deploy the bean directly to an app server (WLS, as it happens). However, I also want the existing integration tests to continue to work. These tests run against their own test database so I need to be able to inject the test configuration when running the integration tests (in a POJO/ non-container environment).
The question is:
Once I've set up my EJB, is there no way that I can override the injected bean without operating inside a container?
Put another way, is there no straightforward way to inject a new JNDI configuration when running the raw POJO integration tests?
An example service resembles the following:
@Stateless(mappedName="MyInterface")
public class MyClassImpl implements MyInterface {
...
@Resource(name="jdbc/MyAppServerDataSourceJNDIName")
DataSource ds;
Connection conn;
...
}
N.B. I don't intend to leave the DataSource and Connection in the services, I just want to get something sensible working before organically refactoring it.
Solutions I'm considering:
- One (pretty awful) notion I have is to just provide a setter on the Connection in the service that is package-private. This way, my junit tests can set the Connection prior to execution. Then, in an app-server environment, the injected DS will be used. Not pretty though.
- I've taken a look at ejb3unit (BaseSessionBeanFixture) and am considering that.
- I also understand that I could create an EJB container in the junit and run within the container. The thing is, I'd like to test the base functionality using straightforward junit tests and against the POJOs (not the EJBs).
- I know this can be done in spring (I'm a bit of an EJB novice) and am considering wiring the EJBs using spring config.
There's a lot of info out there but nothing specific (mostly JPA). Some good pointers elsewhere on SO though.
Thanks in advance.
My recommendations:
- Remove the
conn
field, and use ds.getConnection()
(and conn.close()
) consistently. In your unit testing environment, you can mock DataSource/Connection (or otherwise provide obtain and provide "real" objects that are connected to a test database).
- Add a setter to the EJB class. In fact, you can annotate the method for injection so that your container and unit test environments are even closer. You state that adding a setter would be "pretty awful", but I'm not sure why; setters are very much in the spirit of dependency injection. I agree that adding the setter to the business interface makes no sense, but it seems fine to add one to the bean class.
Example of #2:
@Resource(name="jdbc/MyAppServerDataSourceJNDIName")
public void setDataSource(DataSource ds) {
this.ds = ds;
}
This is the skeleton of the additional boilerplate to provide an injectable Connection to your service (package-private only) so that the EJB is usable as an EJB in an app-container but also as a POJO for junit testing with locally configurable Connection. Note, there is additional boiler-plate and we rely on tests calling #closeConnection to manage it properly.
This solution harks back to the original question and bkail's response:
@Stateless(mappedName = "MyClass")
...
public class MyClass {
// DataSource as configured for app-server environment
@Resource(name="app-dataSource")
private DataSource appDataSource;
private Connection conn;
/**
* Injectable {@code Connection} for injection by unit tests.
*/
/*package-private*/ void setConnection(Connection conn) {
this.conn = conn;
}
/**
* Get {@code Connection}. Use injected Connection if supplied, otherwise
* obtain one from the datasource. Client is responsible for closing this
* through call to #closeConnection (only!).
*/
final Connection getConnection() {
if (this.conn != null) {
return this.conn;
} else {
//Needs exception handling
//
return getDataSource().getConnection)
}
}
/*package-private*/ final void closeConnection(Connection conn) {
if (conn != null && conn != this.conn) {
try{
conn.close();
} catch ... {}
}
}
/*
* Accessor for the {@code DataSource}.
* @return the DataSource
*/
private DataSource getDataSource() {
if (ds == null) {
...
ds = (DataSource) ctx.lookup(jndiName);
...
}
return ds;
}
...
}
public class TestMyClass {
private Connection conn = null;
@BeforeClass
public void setUpBeforeClass() throws Exception {
...
conn = DriverManager.getConnection(...);
}
@Test
public void testMyMethod {
MyClass mc = new MyClass();
mc.setConnection(conn); // Set our own connection
// do test stuff
...
}
...
}