Like it or not, you will find your class using some legacy singleton code. Problem begins when you try to unit test your class. Lets dive into some example and see how to write unit test for such a class.

Singleton.java is typical singleton implementation. Our new class ClassUsingSingleton.java is using Singleton.

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

public class Singleton {

	private static final Singleton instance = new Singleton();
	private String hostName;
	private String port;

	private Singleton(){
		try{
			readProperties("relativePathInProdSystem"+File.separator+"singleton.properties");
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	public static Singleton getInstance(){
		return instance;
	}

	private void readProperties(String fileName) throws Exception{
		File file = new File(fileName);
                Properties pro = new Properties();
	        FileInputStream in = new FileInputStream(file);
	        pro.load(in);
	        hostName = pro.getProperty("hostname");
	        port = pro.getProperty("port");
	}

	public String getHostName() {
		return hostName;
	}

	public String getPort() {
		return port;
	}

}

Lets assume that the ClassUsingSingleton.java is user for the Singleton.

import java.util.List;

import com.mycompany.dao.DaoService;

public class ClassUsingSingleton {

	private Singleton singleton = Singleton.getInstance();
	private DaoService daoService;

	public ClassUsingSingleton(DaoService daoService) {
		this.daoService = daoService;
	}

	public List<string> getHostLocationFromDB() throws Exception {
		return daoService.getHostLocationFromDB(singleton.getHostName());
	}

	public void updateHostLocation(String hostname, String location)
			throws Exception {
		daoService.updateHostLocation(singleton.getHostName(),
				"someLocation");
	}

}

If you try to run following Junit test, it will throw exception because Singleton will try to load file from some relative path which is not available in your unit test.

import static org.easymock.EasyMock.expect;
import static org.powermock.api.easymock.PowerMock.createMock;
import junit.framework.Assert;
import org.easymock.EasyMock;
import org.junit.Test;

import com.mycompany.dao.DaoService;

public class ClassUsingSingletonUnitTest {
         //will throw IOException and test will fail.
	@Test
	public void testGetHostLocationFromDBForSuccess(){
		//create mock for data access
		DaoService mockService = createMock(DaoService.class);
                 try {
			expect(
					mockService.getHostLocationFromDB((String) EasyMock
							.anyObject())).andReturn(new ArrayList<string>());
			ClassUsingSingleton classUsingSingleton = new ClassUsingSingleton(
					mockService);
                        replay(mockService);
			classUsingSingleton.getHostLocationFromDB();
		} catch (Exception e) {
			Assert.fail("Unexpected Exception");
			e.printStackTrace();
		}
	}
}

Problem here is Singleton because it is trying to load properties from a defined location, in real world it may be getting database connection, JMS connection or having remote reference of an EJB. With all these kind of initialization in private constructor, we can not unit test singleton with simply using mock object.

To unit test, we want:

  • Not to invoke private constructor of Singleton.java and
  • initialize singleton object in ClassUsingSingleton.java with our own mock

Here comes powermock. to the rescue.

	@Test
	public void testGetHostLocationFromDBForSuccess(){
	/*************mocking singleton******************/
        //Tell powermock to not to invoke constructor
        //import import static org.powermock.api.easymock.PowerMock.suppressConstructor;
        suppressConstructor(Singleton.class);
        //mock static
        mockStatic(Singleton.class);
        //create mock for Singleton
        Singleton mockSingleton = createMock(Singleton.class);
        //set expectation for getInstance()
        expect(Singleton.getInstance()).andReturn(mockSingleton).anyTimes();
	//set expectation for getHostName()
        expect(mockSingleton.getHostName()).andReturn("myhostName");
        replay(Singleton.class);
        replay(mockSingleton);
        /*******************************/
               //create mock for data access
		DaoService mockService = createMock(DaoService.class);
                 try {
			expect(
					mockService.getHostLocationFromDB((String) EasyMock
							.anyObject())).andReturn(new ArrayList<string>());
			ClassUsingSingleton classUsingSingleton = new ClassUsingSingleton(
					mockService);
                        replay(mockService);
			classUsingSingleton.getHostLocationFromDB();
		} catch (Exception e) {
			Assert.fail("Unexpected Exception");
			e.printStackTrace();
		}
	}
}

Why to avoid singleton?

Following are some of many reasons for it.

  • It promotes tight coupling between classes, class know where to get instance. Any changes in the singleton may break the class that is using it.
  • Singleton does not help with backward compatibility without changing client code. Unit testing is very difficult, it may start breaking in client code if you enhance singleton implementation.

Alternative…Dependency Injection

Program to an interface, not to an implementation. If we follow this GOF advice, we will be able to create well testable and decoupled code.

We can put property loading, resource initialization, service location functionality in application initialization and pass the reference to the application code in constructor.

EJB3 has added Application Startup and Shutdown callbacks. If you are using EJB2.1 you can still initialize in Servlet and push the dependency object in JNDI.

public class ClassUsingDI {
    IServiceLocator serviceLocator;
    public ClassUsingDI (IServiceLocator serviceLocator) {
        this.serviceLocator = serviceLocator;
    }
    public void myBusinessMethod(){
        serviceLocator.locate("myservice").updateAmount(10);
   }
}