For integration testing’s sake, use one interface + one implemenation

With mocking technology today, you may dismiss the idea of ‘one interface + one implementation’ paradigm.

In fact, you may still appreciate this way of code organization.

Let me ask you a question: how to test a non-public method ?

For unit tests, you can set the method as package-private, and put your unit test class under the same package.

But for integration tests, normally you don’t put the integration test classes under the same package. Even if you do, when you need to mock some non-public methods of a dependent class which is not in the same package, you won’t get what you want. Take a look at the following example:

package service;

import dao.DAO;

public class Service {

    DAO dao;


    public void doSth(){
        dao.insertSth();
    }
}

package dao;


public class DAO {

    public void insertSth() {
        String thing = findOut();
        //insert it...
    }

    private String findOut() {
        return "blabla";
    }


}

You want to integration test Service.doSth(), and you want to mock DAO.findOut(). What to do? You have 3 options

  • mock DAO.findOut() anyway, which involves java reflection
  • change DAO.findOut() to a protected method , create a mocking subclass to override the method, and inject this class to Service
  • change DAO.findOut() to a public method

 

The problem of option1 is that it makes findOut() refactor-unfree. The IDE won’t know that this method is referenced by some integration test when you change the name of it.
Option2 is acceptable, but writing a subclass is not so "clean", especially when IoC framework is used.
Option3 is the easiest way, but it is really awkward to expose a private API as a public method

As a purist, you can’t accept Option3. However, you may be willing to bend the rule a little bit. And that’s our solution:

//make DAO an interface
public interface DAO {
    public void insertSth();
}


public class DAOImpl implements  DAO {

    @Override
    public void insertSth() {
        String thing = findOut();
        //insert it...
    }

    //make this method public and mock it in your test
    public String findOut() {
        return "blabla";
    }

}

Let the method being mocked be a public method in the implementation class !
Since it’s public, it can be mocked easily in test code; And serious consumer of DAO will ignore the implementation, so DAOImpl.findOut() won’t be called in any serious code. Problem solved.

Leave a Comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.