yaromir
yaromir

Reputation: 385

Java. Mock data access object

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:

In UserDao:

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.

Upvotes: 0

Views: 2656

Answers (3)

gknicker
gknicker

Reputation: 5569

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);
    }
}

Upvotes: 1

Rogério
Rogério

Reputation: 16380

You can unit test the LoginAction class from the gist as shown in the following test, which uses the JMockit mocking library:

public class LoginTest
{
    @Tested LoginAction action;
    @Mocked ActionMapping mapping;
    @Mocked HttpServletRequest request;
    @Mocked HttpServletResponse response;
    @Capturing UserDao userDao;

    @Test
    public void loginUser()
    {
        final String username = "user";
        final String password = "password";
        LoginForm form = new LoginForm();
        form.setUsername(username);
        form.setPassword(password);

        final ActionForward afterLogin = new ActionForward("home");

        new Expectations() {{
            userDao.checkCredentials(username, password); result = true;
            mapping.findForward("successful-login"); result = afterLogin;
        }};

        ActionForward forwardTo = action.execute(mapping, form, request, response);

        assertSame(forwardTo, afterLogin);
        new Verifications() {{ userDao.terminate(true); }};
    }
}

Upvotes: 1

Luiggi Mendoza
Luiggi Mendoza

Reputation: 85779

Use a proper mock framework like PowerMock or EasyMock (or both) or Mockito and do concrete unit testing for the class implementation. Do not create a UserDaoMock class implementation because then you're not covering the code in UseDaoImpl class, which is one of the advantages of unit testing.

Upvotes: 2

Related Questions