I have this code to connect with a Database, which is Defined by a Strutsapplication.
public void initConnection()
{
if (this.con == null) {
try
{
Context ctx = new InitialContext();
Context envContext = (Context)ctx.lookup("java:comp/env");
this.ds = ((DataSource)envContext.lookup("jdbc/FooBar"));
if (this.ds != null)
{
this.con = this.ds.getConnection();
if (this.con != null) {
LOGGER.info("datasbase connection established");
} else {
LOGGER.error("there was an error during connectiong to the database");
}
}
}
catch (Exception e)
{
LOGGER.error(e);
}
}
}
Right now, the Context is defined using a context.xml!
<Context>
<Resource name="jdbc/FooBar" auth="Container" type="javax.sql.DataSource"
maxActive="20" maxIdle="30" maxWait="10000" username="root" password=""
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/ProductiveDB?autoReconnect=true" />
</Context>
Is there any way to Mock this Context using Java? I am writing Unit tests, and I need to connect to a Database called TestDB.
EDIT:
I am Using mockito to create mock my classes. I would just like to create something similar to the XML just using Java, like for example:
Resource resource = new Resource();
resource.setAuth("Container");
bla bla bla
resource.setURL("jdbc:mysql://localhost:3306/TestDB?aztoReconnect=true");
and then add this to my Context with
new InitialContext().addToContext("jdbc/FooBar",resource);
EDIT II:
I have edited my code inside my setup function to look like this:
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/FooBar");
ds.setUsername("root");
ds.setPassword("");
Context ctx = new InitialContext();
System.out.println(ctx);
System.out.println(ctx.getEnvironment());
ctx.addToEnvironment("java:comp/env/jdbc/FooBar", ds);
I think, this way, I got a lot closer to my solution, but now I get the following error:
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
at javax.naming.spi.NamingManager.getInitialContext(Unknown Source)
at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)
at javax.naming.InitialContext.getEnvironment(Unknown Source)
at com.foo.bar.su.tests.StepDefs.prepareTests(StepDefs.java:55)
at com.foo.bar.su.tests.HomeTest.setUp(HomeTest.java:22)
I could figure out, that this error obviously means, that my env. can not be found. But how can I "create" it, or something?
You should avoid to lookup programmatically as it makes your code harder to test. It's better to use a framework to control the dependency injection. Then you can use something like DBUnit.
If it's not possible to use the dependency injection you can still turn your code more testable. For instance, you can isolate the datasource lookup as follow:
public abstract class MyAbstractDao {
private DataSource ds = null;
protected DataSource getDataSource() {
if (ds == null) {
Context ctx = new InitialContext();
Context envContext = (Context)ctx.lookup("java:comp/env");
return (DataSource)envContext.lookup("jdbc/FooBar");
}
return ds;
}
public void setDataSource(DataSource ds) {
this.ds = ds;
}
}
EDIT: You can use apache DBCP for instance and create the datasource programmatically:
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/TestDB");
ds.setUsername("root");
ds.setPassword("");
Now you can inject the desired datasource used in your tests
I use JMockit (my favorite mocking toolkit) to do this.
#Mocked(stubOutClassInitialization=true) final InitialContext unused = null;
EDIT 1:
In another case, I have something like this:
new Expectations() {
InitialContext ctx;
{
...
ctx = new InitialContext(); // expect constructor
ctx.lookup(anyString); result = myMockThingie;
...
}
}
In my cases I'm not interested particularly to connect to an alternate database, so it's not exactly the circumstance you want, although it seems easy enough to tailor to your needs.
EDIT 2:
Thinking about this some more, maybe you could dodge the mocking altogether here. Why not just, for the purposes of your test, create a subclass of your test subject and override that initConnection() method to use the connection you want? I use this technique often enough and maybe that's the easiest thing for you here. Note that you can create a static inner class right in your test class which subclasses the test subject, so no worries about cluttering up your production code with artifacts specifically for your tests. Hope it helps!
Related
We use EJB3 and JBOSS application Server in our Application. I have a Bean lookup utility method where its a generic method written to lookup stateless EJB Beans by JNDI name:
public class BeanFactory {
static Logger logger = LogManager.getLogger(BeanFactory.class.getName());
/**
*
* #param jndiName
* #return
*/
public static <T> T lookup(String jndiName){
logger.info("Inside bean BeanFactory lookup: " + jndiName);
T handle = null;
try {
InitialContext ctx = new InitialContext();
handle = (T) ctx.lookup(jndiName);
} catch (Exception e) {
logger.error(e, e.fillInStackTrace());
}
return handle;
}
So there are classes which have dependencies on Beans and they use lookup method to invoke the methods of the Bean. For Example
private AuthenticationProfileDTO getAuthenticationProfile(String credId) throws OneM2MException {
ResourceProceduresDao dao = BeanFactory.lookup(ResourceProceduresDao.JNDI_NAME);
AuthenticationProfileRemote apRemote = BeanFactory.lookup(AuthenticationProfileRemote.JNDI_NAME);
AuthenticationProfileDTO authenticationProfileDTO;
if (isKpsaId(credId))
authenticationProfileDTO = apRemote.getAuthenticationProfileDTOForSymmKeyID(credId);
else
authenticationProfileDTO = apRemote.getAuthenticationProfileDTOForCredentialID(credId);
return authenticationProfileDTO;
}
So now when we ran JProfiler on the code the lookup method is coming to be time consuming because every time lookup is called a new InitialContext is instantiated.
I was thinking of making the InitialContext static so that only once it's initialized in a static block, but I don't know what implications will it have in terms of getting Bean instances. Since this piece of code is managed by EJB Container, the run time impacts are unknown. After looking up some articles online not much clarity was there.
Any help is appreciated.
Note that javadoc for InitialContext warns that:
An InitialContext instance is not synchronized against concurrent
access by multiple threads. Multiple threads each manipulating a
different InitialContext instance need not synchronize.
Threads that need to access a single InitialContext instance
concurrently should synchronize amongst themselves and provide the
necessary locking.
So, making the field static isn't necessarily a good idea as you'll need to synchronize each lookup(jndiName) call, and this may cause other issues as per comment by James R. Perkins.
However as you have shown that getAuthenticationProfile(String credId) calls lookup twice, there is no reason why you can't make a BeanFactory hold one InitialContext to reduce the number of instances by re-using InitialContext within same calling methods.
public class BeanFactory {
private final InitialContext ctx;
private BeanFactory(InitialContext initialContext) {
this.ctx = initialContext;
}
private static final Logger logger = LogManager.getLogger(BeanFactory.class.getName());
/** JNDI lookup*/
public <T> T lookup(String jndiName){
// logger.info("Inside bean BeanFactory lookup: " + jndiName);
try {
return (T) ctx.lookup(jndiName);
} catch (Exception e) {
RuntimeException re = new RuntimeException("Could not find jndi: "+jndiName, e);
logger.error(re);
throw re;
}
}
/** Setup a new BeanFactory */
public static BeanFactory create() {
try {
return new BeanFactory(new InitialContext());
} catch (Exception e) {
throw new RuntimeException("Could not create a new context", e);
logger.error(re);
throw re;
}
}
This allows getAuthenticationProfile to use a single InitialContext for 2 lookups:
BeanFactory ctx = BeanFactory.create();
ResourceProceduresDao dao = ctx.lookup(ResourceProceduresDao.JNDI_NAME);
AuthenticationProfileRemote apRemote = ctx.lookup(AuthenticationProfileRemote.JNDI_NAME);
You might also consider whether saving BeanFactory as a thread local would help though I would be very concerned about doing this an application server because you may have little control over which and how many threads instantiate InitialContext and what from what context they run. However it might be suitable within a standalone client program accessing your EJB server logic:
private static final ThreadLocal<BeanFactory> BEANS = ThreadLocal.withInitial(BeanFactory::create);
private static BeanFactory local() {
return BEANS.get();
}
// Example lookups:
ResourceProceduresDao dao = BeanFactory.local().lookup(ResourceProceduresDao.JNDI_NAME);
AuthenticationProfileRemote apRemote = BeanFactory.local().lookup(AuthenticationProfileRemote.JNDI_NAME);
I am writing simple CRUD application which would fetch person records from database and I'm using SparkJava framework I have working code which fetches records from database but I would want to extract JOOQ DSLContext code and inject it as a bean and initialize it in another class in order to have more cleaner code, but I'm not sure how to achieve it here's main method which currently hold everything:
public static void main(String[] args) throws IOException {
final BasicDataSource ds = new BasicDataSource();
final Properties properties = new Properties();
properties.load(BankApiApplication.class.getResourceAsStream("/application.properties"));
ds.setDriverClassName(properties.getProperty("db.driver"));
ds.setUrl(properties.getProperty("db.url"));
ds.setUsername(properties.getProperty("db.username"));
ds.setPassword(properties.getProperty("db.password"));
final ConnectionProvider cp = new DataSourceConnectionProvider(ds);
final Configuration configuration = new DefaultConfiguration()
.set(cp)
.set(SQLDialect.H2)
.set(new ThreadLocalTransactionProvider(cp, true));
final DSLContext ctx = DSL.using(configuration);
final JSONFormat format = new JSONFormat().format(true).header(false).recordFormat(JSONFormat.RecordFormat.OBJECT);
port(8080);
get("/persons", (request, response) -> {
return ctx.select().from(Person.PERSON).fetch().formatJSON();
});
}
How I could extract code which initializes Datasource and configures DSLContext and instead I could just inject DSLContext or some kind of DSLContextHolder and do querying ?
So, in general, you want to inject the highest-level object you can. This is related to the Law of Demeter, which in short says that a component can know about its direct dependencies, but it shouldn't know about those dependencies' dependencies.
In your case, you're really only using DSLContext (ctx). [A note here: your code has a lot of two-letter names - it's pretty hard to follow. It would be easier if you wrote out e.g. ctx -> dslContext, cp -> connectionProvider]. This means you really only want your method to know about the DSLContext, not its dependencies. Therefore, it would be good to pull the following out into a module, then inject just a DSLContext:
Configuration
ConnectionProvider
Properties
BasicDataSource
If all these things are only used in this one main(), you can write a single Provider to return a DSLContext. If some of these are used in multiple places (for more than instantiating this main()'s DSLContext), then they can go in their own Providers. For example, here's what a Provider for a DSLContext would look like, if Configuration was placed in its own Provider:
public class MyModule extends AbstractModule {
// other providers
// ...
#Provides
#Singleton
public DSLContext dslContext(Configuration configuration) {
return SL.using(configuration);
}
}
Then, in your main(), you would write:
Injector injector = Guice.createInjector(yourModule());
DSLContext myContext = injector.getInstance(DSLContext.class);
// ... use it
I'm using a JNDI for creating connection pool. It works great in a web application. I believe the InitialContext is provided by the tomcat server.
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
dataSource = (DataSource)envContext.lookup("jdbc/testdb");
But when I try to call the same utility from a standalone Java program, the initContext object is null. How can I explicitly provide all the necessary properties that Context object is expecting.
Error : 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
Here is an example adapted from the accepted answer but doing everything inline to avoid creating extra classes.
public static void main(String[] args) {
setupInitialContext();
//do something that looks up a datasource
}
private static void setupInitialContext() {
try {
NamingManager.setInitialContextFactoryBuilder(new InitialContextFactoryBuilder() {
#Override
public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException {
return new InitialContextFactory() {
#Override
public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
return new InitialContext(){
private Hashtable<String, DataSource> dataSources = new Hashtable<>();
#Override
public Object lookup(String name) throws NamingException {
if (dataSources.isEmpty()) { //init datasources
MysqlConnectionPoolDataSource ds = new MysqlConnectionPoolDataSource();
ds.setURL("jdbc:mysql://localhost:3306/mydb");
ds.setUser("mydbuser");
ds.setPassword("mydbpass");
dataSources.put("jdbc/mydbname", ds);
//add more datasources to the list as necessary
}
if (dataSources.containsKey(name)) {
return dataSources.get(name);
}
throw new NamingException("Unable to find datasource: "+name);
}
};
}
};
}
});
}
catch (NamingException ne) {
ne.printStackTrace();
}
}
You could also create your own custom context.
LocalContext ctx = LocalContextFactory.createLocalContext();
ctx.addDataSource("jdbc/testdb", driverName, url, usr, pwd);
See Running Beans Locally that use Application Server Data Sources for more details.
UPDATE
You can use the class org.springframework.mock.jndi.SimpleNamingContextBuilder of Spring. e.g.:
Setup:
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("jdbc/Oracle", ods);
builder.activate();
Use:
DataSource ds = InitialContext.doLookup("jdbc/Oracle");
You can create your own Context by sub-classing javax.naming.InitialContext and implementing only a small subset of methods, typically the bind and the lookup methods.
Then you can create your data source and bind it to your initial context to a specific key. After this you are ready to go and query from any place your JNDI context in your stand-alone Java programme.
This is the code you can use to create your own context:
InitialContext initialContext = new InitialContext() {
private Map<String, Object> table = new HashMap<>();
public void bind(String key, Object value) {
table.put(key, value);
}
public Object lookup(String key) throws NamingException {
return table.get(key);
}
};
// Activate the initial context
NamingManager.setInitialContextFactoryBuilder(environment -> environment1 -> initialContext);
Then you can initialise your data source, whichever you choose:
InitialContext ic = new InitialContext();
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
bds.setUrl(url);
bds.setUsername(username);
bds.setPassword(password);
ic.bind(jndiPath, bds);
And somewhere else in your code, you can use the existing data source by retrieving it from the JNDI context:
InitialContext ic2 = new InitialContext();
DataSource ds = (DataSource) ic2.lookup(jndiPath);
assertNotNull(ds);
Connection conn = ds.getConnection();
assertNotNull(conn);
conn.close();
There isn't a way to directly use the Tomcat Context Factory, see here for a little more documentation on the alternatives. But I recommend you try running a registry outside of Tomcat...
// select the registry context factory for creating a context
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
// specify where that factory is running.
env.put(Context.PROVIDER_URL, "rmi://server:1099");
// create an initial context that accesses the registry
Context ctx = new InitialContext(env);
You could change your code in Tomcat to also use this external RegistryContext and then both set(s) would be using the same JNDI provider. This question seems very similar.
Tomcat provides Context & DataSource implementations that work with the InitialContext class. When running Tomcat the Context.INITIAL_CONTEXT_FACTORY property is set to point to Tomcat's implementations. When not running Tomcat, you don't have this ability... you need to use a third-party-library like c3p0 for connection pooling.
You can create an initial context using blow code.
InitialContext ic = new InitialContext();
// Retrieve the Home interface using JNDI lookup
Object helloObject = ic.lookup("java:comp/env/ejb/HelloBean");
if you want create custom initial context, you can extends javax.naming.InitailContext class
You can configure a standalone app (or unit tests!) to use your server's INITIAL_CONTEXT_FACTORY without running the server.
This is how to do that for a Apache Tomcat initialization, but you should have enough information to adapt it for another server.
Starting configuration from the server
This example presumes you want to mimic something like this from your context.xml file:
<Resource name="jdbc/testdb" auth="Container"
type="javax.sql.DataSource"
driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
username="someuser" password="somepassword"
url="jdbc:sqlserver://localhost\SQLEXPRESS;databaseName=somedb;applicationName=someapp"/>
Create the resource to go into the Context
This example is database specific, but you can put things other than a DataSource in the Context. Here is one way you might be able to create a DataSource for this SQL Server example. (It requires the JDBC driver and tomcat-dbcp.jar in your classpath. This code is for an older version of the driver, new versions probably have SQLServerXADataSource and would not need tomcat-dbcp.jar.)
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(SQLServerDriver.class.getName());
dataSource.setUrl(url);
dataSource.setUsername(getUserName());
dataSource.setPassword(getPassword());
Initialize using Tomcat INITIAL_CONTEXT_FACTORY
See this old blog post.
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
InitialContext ctxt = new InitialContext();`
The reference to org.apache.naming.java.javaURLContextFactory means you should have catalina.jar in your classpath. You should be able to find variants for the INITIAL_CONTEXT_FACTORY and URL_PGK_PREFIXES values for other application servers.
Register the resource for future lookups
Then register your DataSource dataSource for future lookups, at java:/comp/env/jdbc/testdb. Unfortunately, you have to make sure all the intermediate subcontext levels are created before you can add the resource. (Exercise for the reader: You can create a method to figure out the subcontext names from the resourceName instead of hard-coding the list as this example does.)
// Create all the intermediate levels
for (String resource : new String[]{ "java:", "java:comp", "java:comp/env", "java:/comp/env/jdbc" })
{
try
{
ctxt.lookup(subContext);
}
catch (NameNotFoundException nnfe)
{
ctxt.createSubcontext(subContext);
}
}
// Finally, register the resource
ctxt.bind(resourceName, dataSource);
This completes the setup portion, mimicking what is normally done in the Tomcat application server context.xml.
Do the lookup
Then the rest of your application can create InitialContexts and do lookups as normal (in a single lookup call):
Context ic = new InitialContext();
DataSource ds = (DataSource)ic.lookup("java:/comp/env/jdbc/testdb");
Firstly, I can't use the declarative #Transactional approach as the application has multiple JDBC data-sources, I don't want to bore with the details, but suffice it to say the DAO method is passed the correct data-source to perform the logic. All JDBC data sources have the same schema, they're separated as I'm exposing rest services for an ERP system.
Due to this legacy system there are a lot of long lived locked records which I do not have control over, so I want dirty reads.
Using JDBC I would perform the following:
private Customer getCustomer(DataSource ds, String id) {
Customer c = null;
PreparedStatement stmt = null;
Connection con = null;
try {
con = ds.getConnection();
con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
stmt = con.prepareStatement(SELECT_CUSTOMER);
stmt.setString(1, id);
ResultSet res = stmt.executeQuery();
c = buildCustomer(res);
} catch (SQLException ex) {
// log errors
} finally {
// Close resources
}
return c;
}
Okay, lots' of boiler-plate, I know. So I've tried out JdbcTemplate since I'm using spring.
Use JdbcTemplate
private Customer getCustomer(JdbcTemplate t, String id) {
return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
}
Much nicer, but it's still using default transaction isolation. I need to somehow change this. So I thought about using a TransactionTemplate.
private Customer getCustomer(final TransactionTemplate tt,
final JdbcTemplate t,
final String id) {
return tt.execute(new TransactionCallback<Customer>() {
#Override
public Customer doInTransaction(TransactionStatus ts) {
return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
}
});
}
But how do I set the transaction isolation here? I can't find it anywhere on the callback or the TransactionTemplate to do this.
I'm reading Spring in Action, Third Edition which explains as far as I've done, though the chapter on transactions continues on to using declarative transactions with annotations, but as mentioned I can't use this as my DAO needs to determine at runtime which data-source to used based on provided arguments, in my case a country code.
Any help would be greatly appreciated.
I've currently solved this by using the DataSourceTransactionManager directly, though it seems like I'm not saving as much boiler-plate as I first hoped. Don't get me wrong, it's cleaner, though I still can't help but feel there must be a simpler way. I don't need a transaction for the read, I just want to set the isolation.
private Customer getCustomer(final DataSourceTransactionManager txMan,
final JdbcTemplate t,
final String id) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
TransactionStatus status = txMan.getTransaction(def);
Customer c = null;
try {
c = t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
} catch (Exception ex) {
txMan.rollback(status);
throw ex;
}
txMan.commit(status);
return c;
}
I'm still going to keep this one unanswered for a while as I truly believe there must be a better way.
Refer to Spring 3.1.x Documentation - Chapter 11 - Transaction Management
Using the TransactionTemplate helps you here, you need to configure it appropriately. The transaction template also contains the transaction configuration. Actually the TransactionTemplate extends DefaultTransactionDefinition.
So somewhere in your configuration you should have something like this.
<bean id="txTemplate" class=" org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="readOnly" value="true" />
<property name="transactionManager" ref="transactionManager" />
</bean>
If you then inject this bean into your class you should be able to use the TransactionTemplate based code you posted/tried earlier.
However there might be a nicer solution which can clean up your code. For one of the projects I worked on, we had a similar setup as yours (single app multiple databases). For this we wrote some spring code which basically switches the datasource when needed. More information can be found here.
If that is to far fetched or overkill for your application you can also try and use Spring's AbstractRoutingDataSource, which based on a lookup key (country code in your case) selects the proper datasource to use.
By using either of those 2 solutions you can start using springs declarative transactionmanagement approach (which should clean up your code considerably).
Define a proxy data source, class being org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy and set the transaction isolation level. Inject actual data source either through setter or constructor.
<bean id="yourDataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<constructor-arg index="0" ref="targetDataSource"/>
<property name="defaultTransactionIsolationName" value="TRANSACTION_READ_UNCOMMITTED"/>
</bean>
I'm not sure you can do it without working with at the 'Transactional' abstraction-level provided by Spring.
A more 'xml-free' to build your transactionTemplate could be something like this.
private TransactionTemplate getTransactionTemplate(String executionTenantCode, boolean readOnlyTransaction) {
TransactionTemplate tt = new TransactionTemplate(transactionManager);
tt.setReadOnly(readOnlyTransaction);
tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
return tt;
}
In any case I would "leverage" the #Transactional annotation specifying the appropriate transaction manager, binded with a separate data-source. I've done this for a multi-tenant application.
The usage:
#Transactional(transactionManager = CATALOG_TRANSACTION_MANAGER,
isolation = Isolation.READ_UNCOMMITTED,
readOnly = true)
public void myMethod() {
//....
}
The bean(s) declaration:
public class CatalogDataSourceConfiguration {
#Bean(name = "catalogDataSource")
#ConfigurationProperties("catalog.datasource")
public DataSource catalogDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = ENTITY_MANAGER_FACTORY)
public EntityManagerFactory entityManagerFactory(
#Qualifier("catalogEntityManagerFactoryBean") LocalContainerEntityManagerFactoryBean emFactoryBean) {
return emFactoryBean.getObject();
}
#Bean(name= CATALOG_TRANSACTION_MANAGER)
public PlatformTransactionManager catalogTM(#Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public NamedParameterJdbcTemplate catalogJdbcTemplate() {
return new NamedParameterJdbcTemplate(catalogDataSource());
}
}
All,
I'm trying to do some unit testing in some archaic java code (no interfaces, no abstraction, etc.)
This is a servlet that uses a ServletContext (which I'm assuming is set up by Tomcat) and it has database information is set up in the web.xml/context.xml file. Now, I've figured out how to make a Fake ServletContext, but the code has
InitialContext _ic = new InitialContext();
all over the place (so it isn't feasible to replace it). I need to find a way to make a default InitialContext() able to do the _ic.lookup(val) without throwing an exception.
I'm assuming there is some way that the context.xml is getting loaded, but how that magic works, I'm drawing a blank. Anyone have any ideas?
Take advantage of the fact that InitialContext uses an SPI to handle its creation. You can hook into its lifecycle by creating an implementation of javax.naming.spi.InitialContextFactory and passing that to your tests via the system property javax.naming.factory.initial (Context.INTITIAL_CONTEXT_FACTORY). It's simpler than it sounds.
Given this class:
public class UseInitialContext {
public UseInitialContext() {
try {
InitialContext ic = new InitialContext();
Object myObject = ic.lookup("myObject");
System.out.println(myObject);
} catch (NamingException e) {
e.printStackTrace();
}
}
}
And this impl of InitialContextFactory:
public class MyInitialContextFactory implements InitialContextFactory {
public Context getInitialContext(Hashtable<?, ?> arg0)
throws NamingException {
Context context = Mockito.mock(Context.class);
Mockito.when(context.lookup("myObject")).thenReturn("This is my object!!");
return context;
}
}
Creating an instance of UseInitialContext in a junit test with
-Djava.naming.initial.factory=initial.context.test.MyInitialContext
on the command line outputs This is my object!! (easy to set up in eclipse). I like Mockito for mocking and stubbing. I'd also recommend Micheal Feather's Working Effectively with Legacy Code if you deal with lots of legacy code. It's all about how to find seams in programs in order to isolate specific pieces for testing.
Here's my solution to setting up the Inintial Context for my unit tests. First I added the following test dependency to my project:
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
<version>6.0.33</version>
<scope>test</scope>
</dependency>
Then I created a static method with the following code:
public static void setupInitialContext() throws Exception {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
InitialContext ic = new InitialContext();
ic.createSubcontext("jdbc");
PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setDatabaseName("postgres");
ds.setUser("postgres");
ds.setPassword("admin");
ic.bind("jdbc/something", ds);
}
Finally in each of my test class I add an #BeforeClass method which calls setupInitialContext.
Try setting up the system variables before:
System.setProperty(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
System.setProperty(Context.URL_PKG_PREFIXES,
"org.apache.naming");
InitialContext ic = new InitialContext();
If you are using JUnit, follow this doc: https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit
You can use PowerMock to mock construction of the InitialContext and control its behavior. Constructor Mocking is documented here.
PowerMock tests can be quite messy and complicated, refactoring is normally a better option.
Today I've faced the same problem (we can't user PowerMock) and solved it this way:
Don't lookup in the constructor so when you invoke #InitMock on the object, the constructor doesn't require the context yet.
Create a method for retrieving the service bean when needed like "getService().serviceMethod(param, param ...)":
/* Class ApplicationResourceProvider */
/* We can mock this and set it up with InjectMocks */
InitialContext ic;
/* method hiding the lookup */
protected ApplicationService getService() throws NamingException {
if(ic == null)
ic = new InitialContext();
return (ApplicationService)ic.lookup("java:global/defaultApplicationLocal");
}
On the test, set it up:
#Mock
ApplicationService applicationServiceBean;
#Mock
InitialContext ic;
#InjectMocks
ApplicationResourceProvider arp;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(ic.lookup(anyString())).thenReturn(applicationServiceBean);
...
}
Have you considered mockito?
It's as easy as:
InitialContext ctx = mock(InitialContext.class);
By the way, should you choose to use mocks i would recommend reading this article as well: http://martinfowler.com/articles/mocksArentStubs.html
A poor man's standalone implementation using no external libraries:
public class myTestClass {
public static class TestContext extends InitialContext {
public TestContext() throws NamingException {
super(true /*prevents initialization*/);
}
static Object someExpectedValue = "the expected string or object instance";
/*override the method(s) called by the legacy program on _ic, check the parameter and return the wanted value */
public Object lookup(String name) throws NamingException {
return name != null && name.equals("theValueOfVal") ? someExpectedValue : null;
}
}
public static class TestInitialContextFactory implements InitialContextFactory {
public Context getInitialContext(Hashtable<?, ?> arg0) throws NamingException {
return new TestContext();
}
}
public static void main(String[] args) throws SQLException {
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "the.package.myTestClass$TestInitialContextFactory");
/*now call the legacy logic to be tested*/
...
You could use a switch in the override of the lookup method to return the expected value for each different val value passed to _ic.lookup(val) throughout the legacy program.