8

I have a resource file which has some settings. I have a ResourceLoader class which loads the settings from this file. This class is currently an eagerly instantiated singleton class. As soon as this class loads, it reads the settings from the file (file path stored as a constant field in another class). Some of these settings are not suitable for unit tests. For example, I have thread sleep time in this file, which may be hours for production code, but I'd like it to be a couple of milliseconds for unit tests. So I have another test resource file which has a different set of values.

How do I go about swapping the main resource file with this test file during unit testing? The project is a Maven project and I'm using TestNG as the testing framework. These are some of the approaches I've been thinking about but none of them seem ideal:

  1. Use @BeforeSuite and modify the FilePath constant variable to point to the test file and use @AfterSuite to point it back to the original file. This seems to be working, but I think because the ResourceLoader class is eagerly instantiated, there is no guarantee that the @BeforeSuite method will always execute before the ResourceLoader class is loaded and hence old properties may be loaded before the file path is changed. Although most compilers load a class only when it is required, I'm not sure if this is a Java specification requirement. So in theory this may not work for all Java compilers.

  2. Pass the resource file path as a command line argument. I can add the test resource file path as command line argument in the surefire configuration in the pom. This seems a bit excessive.

  3. Use the approach in 1. and make ResourceLoader lazy instantiated. This guarantees that if @BeforeMethod is called before the first call to ResourceLoader.getInstance().getProperty(..), ResourceLoader will load the correct file. This seems to be better than the first 2 approaches, but I think making a singleton class lazy instantiated makes it ugly as I can't use a simple pattern like making it an enum and such (as is the case with eager instantiation).

This seems like a common scenario, what is the most common way of going about it?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Marko Cain
  • 328
  • 3
  • 13
  • Is reworking your application to use a better configuration library an option? – Thorbjørn Ravn Andersen Jan 02 '20 at 15:00
  • Yes, I just want to know the standard way of doing this. I'm open to reworking the application. – Marko Cain Jan 04 '20 at 20:08
  • The only standard way is to use environment and/or system properties. That is usually too limiting because they are hard to both provide and override from the command line in e.g tests. I would suggest you add information to your question about what you need and how you expect to be able to configure your application with a few use cases. You may want to think of passing in the configuration values in the constructor of the classes needing them. – Thorbjørn Ravn Andersen Jan 04 '20 at 21:54

2 Answers2

7

All singletons either eagerly or lazily instantiated are anti-pattern. Usage of singletons makes unit testing harder because there is no easy way to mock singleton.

Mock static method

A workaround is to use PowerMock to mock static method returning singleton instance.

Use dependency injection

A better solution is to use dependency injection. If you already use a dependency injection framework (e.g. Spring, CDI), refactor the code to make ResourceLoader a managed bean with scope singleton.

If you don't use a dependency injection framework, an easy refactoring will be to make changes to all classes using the singleton ResourceLoader:

public class MyService {

  public MyService() {
    this(ResourceLoader.getInstance());
  }

  public MyService(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }
}

And then in unit tests mock ResourceLoader using Mockito

ResourceLoader resourceLoader = mock(ResourceLoader.class);
when(ResourceLoader.getProperty("my-property")).thenReturn("10");
MyService myService = new MyService(resourceLoader);

Externalise configuration

Another approach is to place a file with test settings under src/test/resources. If you store settings in the src/main/resources/application.properties, a file src/test/resources/application.properties will override it.

Also, externalising configuration to a file not packaged in a JAR is a good idea. This way, file src/main/resources/application.properties will contain default properties and a file passed using command-line parameter will override these properties. So, a file with test properties will be passed as a command line parameter too. See how Spring handles externalised configuration.

Use Java System Properties

Even easier approach is to allow overriding of default properties with System Properties in the method ResourceLoader.getInstance().getProperty() and pass test properties this way

public String getProperty(String name) {
  // defaultProperties are loaded from a file on a file system:
  // defaultProperties.load(new FileInputStream(new File(filePath)));
  // or from a file in the classpath:
  // defaultProperties.load(ResourceLoader.class.getResourceAsStream(filePath));
  return System.getProperty(name, defaultProperties.get(name));
}
Eugene Khyst
  • 9,236
  • 7
  • 38
  • 65
0

Check if you in jUnit

You can also check in runtime whether jUnit is running and then exchange the path. That would work like this (untested):

public static funktionToTest() {
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
    List<StackTraceElement> list = Arrays.asList(stackTraceElements);
    String path = "/origin/path/to/your/file"; // here can you set the default path to your rescource
    for (StackTraceElement stackTraceElement : list) {
        if (stackTraceElement.getClassName().startsWith("org.junit.")) {
            path = "/path/only/in/jUnit/test"; // and here the normal path
        }           
    }
    //do what you want with the path
}