I've encountered problem with mocking data access logic.
I'm developing web application using JavaEE, Struts and my custom data access logic. In this application Struts Action operates with UserDao to retrieve User objects. Lifecycle of the UserDao object is tied to the JDBC transaction. Idea is that when Action creates UserDao object it starts JDBC transaction (necessary JDBC stuff is stored inside UserDao object), all invocations of UserDao methods operate in single JDBC transaction and then Action terminates UserDao object finishing transaction (with either commit or rollback).
The problem is that during tests of the Actions i need to mock this UserDao to make it return User objects with necessary test data.
The only solution i found so far is horrible. I've made following: splitted UserDao into UserDao interface and UseDaoImpl class that implements it. This interface will also be implemented by UserDaoMock that will be returning necessary test data. Next i need some factory that will return real UserDao (UserDaoImpl) in production run and mock (UserDaoMock) in test run. This factory should not depend from UserDaoMock type to make production code of the application independent from mock .class file. This results into horrible design.
Shortcomings:
In factory:
I need an instance of UserDao inside factory just to be able to call instantiate
method (which is in fact copy constructor, but i need it to be a method to be able to use polymorphism to choose between UserDaoImpl or UserDaoMock) to create another object of UserDao that will be associated with transaction (depending on runtime type it will be instantiate
method of either UserDaoImpl or UserDaoMock).
public class UserDaoFactory {
private static UserDaoFactory instance;
private UserDao exampleUserDao;
public static UserDaoFactory getInstance() {
if(instance == null) {
instance = new UserDaoFactory();
// By default we initialize factory with example of real UserDao.
// In test will be set to example of UserDaoMock with setExampleUserDao method.
instance.setExampleUserDao(new UserDaoImpl(true));
}
return instance;
}
public UserDao getUserDao() {
return exampleUserDao.instantiate(true);
}
public void setExampleUserDao(UserDao userDao) {
this.exampleUserDao = userDao;
}
}
In UserDao:
I need both instantiate
method (which in fact is copy constructor).
Despite that i also need public constructor to initialize factory with example.
Constructor should have parameter that tells whether it is example or not (whether to start transaction or not).
public final class UserDaoImpl implements UserDao {
private DataSource ds;
private Connection conn;
public UserDao instantiate(boolean startTransaction) {
if(startTransaction) {
return new UserDaoImpl(true);
} else {
return new UserDaoImpl(false);
}
}
public UserDaoImpl(boolean initiate) {
if(initiate) {
// DB connection initialization and start of the transaction
} else {
new UserDaoImpl();
}
}
private UserDaoImpl() {
}
@Override
public void terminate(boolean commit) {
// End of the transaction and DB connection termination
}
// Other interface methods implementation ommited
}
Is there a way to make proper design for testability in this case?
Or, if not, maybe the cause of the problem is that i decided to tie UserDao lifecycle to JDBC transaction? What is possible alternatives? To make UserDao singleton? Won't this be an application bottleneck if all DB interaction will be done via single object.
Or, can you suggest another data access pattern that is more easy to mock?
Please, help. It is the most horrible design i did in my life.
Thanks in advance.
What you need is the simplest thing that could possibly work. Here is a Hello World example for just that.
/* DAO with a data access method */
public class HelloWorldDAO
{
public String findGreeting()
{
return "Hello from the database!";
}
}
/* Struts Action class with execute method */
public class HelloWorldAction implements Action
{
private String greeting;
/* Uses indirection for DAO construction */
@Override
public String execute() throws Exception {
HelloWorldDAO dao = newHelloWorldDAO();
setGreeting(dao.findGreeting());
return SUCCESS;
}
/* The simplest possible dependency injection */
protected HelloWorldDAO newHelloWorldDAO() {
return new HelloWorldDAO();
}
public String getGreeting() {
return this.greeting;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
}
/* Unit tests for HelloWorldAction */
public class HelloWorldActionTest
{
@Test
public void testExecute() throws Exception
{
final String expectedGreeting = "Hello Test!";
String expectedForward = "success";
HelloWorldAction testAction = new HelloWorldAction() {
/* Override dependency injection method to substitute a mock impl */
@Override
protected HelloWorldDAO newHelloWorldDAO()
{
return new HelloWorldDAO() {
@Override
public String findGreeting()
{
return expectedGreeting;
}
};
}
};
String actualForward = testAction.execute();
String actualGreeting = testAction.getGreeting();
assertEquals("forward", expectedForward, actualForward);
assertEquals("greeting", expectedGreeting, actualGreeting);
}
}