Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring static method / static field for Testing

I have the following legacy code:

public class MyLegacyClass
{
    private static final String jndiName = "java:comp/env/jdbc/LegacyDataSource"

    public static SomeLegacyClass doSomeLegacyStuff(SomeOtherLegacyClass legacyObj)
    {
       // do stuff using jndiName
    }
}

This class is working in a J2EE-Container.

Now I would like to test the class outside of the container.

What is the best strategy? Refactoring is basically allowed.

Accessing the LegacyDataSource is allowed (the test does not have to be a "pure" unit-test).

EDIT: Introducing additional runtime-frameworks is not allowed.

like image 528
jbandi Avatar asked Oct 30 '08 13:10

jbandi


3 Answers

Just to make @Robin's suggestion of a strategy pattern more concrete: (Notice that the public API of your original question remains unchanged.)

public class MyLegacyClass {

  private static Strategy strategy = new JNDIStrategy();

  public static SomeLegacyClass doSomeLegacyStuff(SomeOtherLegacyClass legacyObj) {
    // legacy logic
    SomeLegacyClass result = strategy.doSomeStuff(legacyObj);
    // more legacy logic
    return result;
  }

  static void setStrategy(Strategy strategy){
    MyLegacyClass.strategy = strategy;
  }

}

interface Strategy{
  public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj);
}

class JNDIStrategy implements Strategy {
  private static final String jndiName = "java:comp/env/jdbc/LegacyDataSource";

  public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) {
    // do stuff using jndiName
  }
}

...and JUnit test. I'm not a big fan of having to do this setup/teardown maintenance, but that's an unfortunate side effect of having an API based on static methods (or Singletons for that matter). What I do like about this test is it does not use JNDI - that's good because (a) it'll run fast, and (b) the unit test should only be testing the business logic in doSomeLegacyStuff() method anyway, not testing the actual data source. (By the way, this assumes the test class is in the same package as MyLegacyClass.)

public class MyLegacyClassTest extends TestCase {

  private MockStrategy mockStrategy = new MockStrategy();

  protected void setUp() throws Exception {
    MyLegacyClass.setStrategy(mockStrategy);
  }

  protected void tearDown() throws Exception {
    // TODO, reset original strategy on MyLegacyClass...
  }

  public void testDoSomeLegacyStuff() {
    MyLegacyClass.doSomeLegacyStuff(..);
    assertTrue(..);
  }

  static class MockStrategy implements Strategy{

    public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) {
      // mock behavior however you want, record state however
      // you'd like for test asserts.  Good frameworks like Mockito exist
      // to help create mocks
    }
  }
}
like image 88
Scott Bale Avatar answered Nov 16 '22 01:11

Scott Bale


Refactor the code to use dependency injection. Then use you preferred DI framework (Spring, Guice, ...) to inject your resources. That will make it easy to switch between resource objects and strategies at runtime.

In this case, you can inject your datasource.

EDIT: Based on your new restriction, you can accomplish the same thing by using a strategy pattern to set your datasource at runtime. You can probably just use a properties file to distinguish which strategy to create and supply the datasource. This would require no new framework, you would just be hand coding the same basic functionality. We used this exact idea with a ServiceLocator to supply a mock datasource when testing outside of the Java EE container.

like image 26
Robin Avatar answered Nov 16 '22 02:11

Robin


I think that the best solution here is bind that JNDI to a local

The legacy Code is using the jndiName like that:

DataSource datasource = (DataSource)initialContext.lookup(DATASOURCE_CONTEXT);

So, The solution here is bind a local (or whatever you have for you test data) into a JNDI like that:

  BasicDataSource dataSource = new BasicDataSource();
  dataSource.setDriverClassName(System.getProperty("driverClassName"));
  dataSource.setUser("username");
  dataSource.setPassword("password");
  dataSource.setServerName("localhost");
  dataSource.setPort(3306);
  dataSource.setDatabaseName("databasename");

And then the binding:

Context context = new InitialContext();
context.bind("java:comp/env/jdbc/LegacyDataSource",datasource); 

Or something similar, hope that helps you.

Good luck!

like image 32
David Santamaria Avatar answered Nov 16 '22 00:11

David Santamaria