How to unit test a Singleton pattern using jmockit

There is a java class, it is based on the Singleton pattern. how to unit test this class? my next code:

public class ConfigFromFile implements ConfigStrategy {
  private String endpoint;
  private final static String CONFIG_FILE = "/conf/config.properties";

  private InputStream getInputStream(String configFilePath) throws FileNotFoundException{
    return new FileInputStream(configFilePath);
  }

  private void initFromFile() {
    Properties prop = new Properties();
    InputStream input = null;
    try {
      input = getInputStream(CONFIG_FILE);
      prop.load(input);
      endpoint = prop.getProperty("endpoint");
    } catch(Exception e) {
      e.printStackTrace();
    } finally {
      if (input != null) {
        try {
          input.close();
        } catch (IOException e) {
        }
      }
    }
  }

  private ConfigFromFile() {
    initFromFile();
  }

  private static class ConfigFromFileHolder {
    private static ConfigFromFile instance = new ConfigFromFile();
  }

  public static ConfigFromFile getInstance() {
    return ConfigFromFileHolder.instance;
  }

  @Override
  public String getEndpoint() {
      return endpoint;
  }
}

      

I need to write a unit test for this class.

  • unit test cannot call external resource, so we need to mock the "/conf/config.properties" file. we can use jmockit.
  • This class is based on the Singleton pattern. We hope that the interaction between these two cases cannot be affected.

In my case:

  • Case1, this is the normal case, the content of the file is "endpoint = www.baidu.com"
  • Case2, this is an abnormal case, we can make fun of this file does not exist.

how to implement these cases? Thank!

+3


source to share


1 answer


You can add a scoped constructor that uses the file path to load and try creating a fake file in the temp folder, then instantiate your test passing the file path. Don't mock.

public class ConfigFromFile implements ConfigStrategy {
  private static final String CONFIG_FILE = "/conf/config.properties";
  private final String configFilePath;
  private String endpoint;

  // Visible for testing
  ConfigStrategy(String configFilePath) {
    this.configFilePath = configFilePath;
  }

  private ConfigStrategy() {
    this(CONFIG_FILE);
  }

  private void initFromFile() {
    try (InputStream input = new FileInputStream(configFilePath)) {
      Properties prop = new Properties();
      prop.load(input);
      endpoint = prop.getProperty("endpoint");
    } catch(Exception e) {
      e.printStackTrace();
    }
  }

  ...

}

      

Note. I've rewritten initFromFile()

to use try-with-resources , which you can do if you are using JDK 7 or higher.

You might want to reconsider whether this class should be static singleton. This not only makes it harder to validate your code, but it also cannot be configured to use different files in different environments. Remember that the test for the class is the first user of the API class . If your test is difficult to write, it could indicate a problem with your API.

If you need to restrict one instance, I suggest using a dependency injection framework like Guice or Spring. If you are using Spring, your class can implement InitializingBean

and load the config file after the class is created, but before it is injected into another class:

public class ConfigFromFile implements ConfigStrategy, InitializingBean {
  private final String configFilePath;
  private String endpoint;

  ConfigStrategy(String configFilePath) {
    this.configFilePath = configFilePath;
  }

  @Override
  public void afterPropertiesSet() throws IOException {
    try (InputStream input = new FileInputStream(configFilePath)) {
      Properties prop = new Properties();
      prop.load(input);
      endpoint = prop.getProperty("endpoint");
    }
  }

  @Override
  public String getEndpoint() {
    return endpoint;
  }
}

      

Yes, that's all the code you need.

  • no need to catch the exception
  • config file loading exception will not be silently ignored.
  • No need to have getEndpoint()

    to check if initialization failed


You can do something similar with Guice.

But wait, if you are using Spring and you are ready to move the endpoint value into Spring config, then it gets even easier!

public class ConfigFromSpring implements ConfigStrategy {
  private final String endpoint;

  ConfigStrategy(String endpoint) {
    this.endpoint = endpoint;
  }

  @Override
  public String getEndpoint() {
    return endpoint;
  }
}

      

The class is simple, it doesn't require any unit tests.

Your XML bean will look like this:

<bean id="config" class="examples.ConfigFromSpring">
  <constructor-arg type="String" value="end of the road"/>
</bean>

      

Of course, at this point I would get rid of ConfigStrategy

and set the config for the object that uses the config.

0


source







All Articles