Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory design pattern - Not use static methods because unit testing is a problem

I know this question has been asked several times in stackoverflow but somehow still had some trouble figuring out a solution. The following example I think is a good case to have static methods

public class ConnectionFactory
{
     public static Connection createConnection(ConnectionType connectionType, String ipAddr, Integer port)
    {
           //Some error checking
         switch(connectionType)
         {   
             case TCP:
                  return createTcpConnection(ipAddr, port);
             case UDP:
                  return createUdpConnection(ipAddr, port);
             case RTP:
                  return createRtpConnection(ipAddr, port);
             case SCTP:
                  return createRtpConnection(ipAddr, port);
             default:
                  break;
         }
    }

    // TcpConnection, RtpConnection, SctpConnection and UdpConnection implement interface Connection
    public Connection createTcpConnection()
    {
        Connection connection = new TcpConnection();
         .....
         .....
         return connection;
    }

    public Connection createUdpConnection()
    {
        Connection connection = new UdpConnection();
        .....
        .....
        return connection;
    }

    ....
    ....
}

And suppose if I have a CommunicationService like the following

public class CommunicationService
{
    public void initConnectionPool(ConnectionType connectionType)
    {
        for(int i = 0; i < MAX_CONNECTIONS; i++)
             connectionList.add(ConnectionFactory.createConnection(connectionType, "domain.com", 40203));

        //Some more code here to do further processing
          ......
          ......
    }    

    //Some more methods
}

Like this, different communication services can create and maintain multiple type of connections.

I want to test initConnectionPool method and in a unit test environment, socket creation will definitely fail.

I can change the ConnectionFactory to a concrete class and mock it out. But isn't this case a good situation to create a class with static methods? I am not trying to maintain any state in ConnectionFactory. So, if using static methods where it may be appropriate may cause testing problems when do we use static methods? Or is it not appropriate to use static methods here?

EDIT: Solution I went with

public class CommunicationService
{
    public void initConnectionPool(ConnectionType connectionType)
    {
        for(int i = 0; i < MAX_CONNECTIONS; i++)
             connectionList.add(connectToHost(connectionType));

        //Some more code here to do further processing
          ......
          ......
    }    

    public Connection connectToHost(ConnectionType connectionType)
    {
        ConnectionFactory.createConnection(connectionType, "domain.com", 40203)
    }
    //Some more methods
}

In the test overrode connectToHost and returned a mock.

like image 324
Prasanna Avatar asked Jul 07 '11 05:07

Prasanna


2 Answers

If you use a library like JMockIt then you can mock out static methods for unit testing.

like image 132
Cameron Skinner Avatar answered Oct 13 '22 00:10

Cameron Skinner


I think you should read this article: Static Methods are Death to Testability (Google Testing Blog).

Despite of your ConnectionFactory class doesn't maintain any state information, I would suggest you to create concrete class and go like this:

public class ConnectionFactory
{
    public Connection createConnection(ConnectionType connectionType, String ipAddr, Integer port)
    {
         //Some error checking
         switch(connectionType)
         {   
             case TCP:
                  return createTcpConnection(ipAddr, port);
             case UDP:
                  return createUdpConnection(ipAddr, port);
             case RTP:
                  return createRtpConnection(ipAddr, port);
             case SCTP:
                  return createRtpConnection(ipAddr, port);
             default:
                  break;
         }
    }

    // TcpConnection, RtpConnection, SctpConnection and UdpConnection implement interface Connection
    public Connection createTcpConnection()
    {
        Connection connection = new TcpConnection();
        ...
        return connection;
    }

    public Connection createUdpConnection()
    {
        Connection connection = new UdpConnection();
        ...
        return connection;
    }
    ...    
}

public class CommunicationService
{
    private ConnectionFactory connectionFactory;

    public CommunicationService()
    {
        this(new ConnectionFactory());
    }

    public CommunicationService(ConnectionFactory factory)
    {
        connectionFactory = factory;
    }

    public void initConnectionPool(ConnectionType connectionType)
    {
        for(int i = 0; i < MAX_CONNECTIONS; i++)
             connectionList.add(connectionFactory.createConnection(connectionType, "domain.com", 40203));
        ...
    }    
    ...
}

The rest of your code will not change at all, but for testing purposes you will be able to create TestConnectionFactory class:

public class TestConnectionFactory : ConnectionFactory
{
    public override Connection createTcpConnection()
    {
        ...
        return testTcpConnection;
    }

    public override Connection createUdpConnection()
    {
        ...
        return testUdpConnection;
    }
}

and use it for testing the CommunicationService like this:

CommunicationService service = new CommunicationService(new TestConnectionFactory());
// Tests
...
like image 44
bniwredyc Avatar answered Oct 13 '22 02:10

bniwredyc