Question

This is strange and I am not sure how to explain clearly. Please bear with me and look the code snippets for details.

I implemented a singleton using enum. The enum has a private constructor in which I am doing few operations and validations before constructing the object. For all these validations I am throwing some exceptions (like IllegalArgumentException).

My test cases consists of tests for both negative and positive scenarios. Whenever there are more than 2 test cases with such mix of negative and positive test cases, I get following exception: java.lang.NoClassDefFoundError: Could not initialize class com.blah.blah.SingletonClass. Please check the complete code below. I am using following tech stack:

JDK 1.7_51
Spring 4.0.0.RELEASE
testng 6.8.7

SingletonUsingEnum.java

public enum SingletonUsingEnum {
  INSTANCE;

  // Logger
  private final Logger logger = LoggerFactory.getLogger(SingletonUsingEnum.class);

  private SingletonUsingEnum() {
    final MyConfig myConfig = MyConfigManager.getMyConfig();
    if(myConfig == null) {
      throw new IllegalArgumentException("MyConfig is null");
    }

    if(StringUtils.isBlank(myConfig.getConfigValue())) {
      throw new IllegalArgumentException("myConfig.configValue value null/empty");
    }

    if(StringUtils.isBlank(myConfig.getOtherConfigValue())) {
      throw new IllegalArgumentException("myConfig.otherConfigValue null/empty");
    }

    logger.info("This is a singleton using enum");
  } 
}

MyConfig.java

public class MyConfig {
  private String configValue;
  private String otherConfigValue;

  public MyConfig(final String configValue, final String otherConfigValue) {
    this.configValue = configValue;
    this.otherConfigValue = otherConfigValue;
  }

  public String getConfigValue() {
    return configValue;
  }

  public String getOtherConfigValue() {
    return otherConfigValue;
  } 
}

MyConfigManager.java

public class MyConfigManager {
  private final static ConcurrentMap myConfigHolder = new ConcurrentHashMap();

  public static void registerMyConfig(final MyConfig myConfig) {
    myConfigHolder.put("myConfig", myConfig);
  }

  public static MyConfig getMyConfig() {
    return myConfigHolder.get("myConfig");
  }

  public static void clearAll() {
    myConfigHolder.clear();
  }
}

SingletonUsingEnumTest.java

@ContextConfiguration(classes = {SingletonUsingEnumTest.SpringConfig.class})
public class SingletonUsingEnumTest extends AbstractTestNGSpringContextTests {
  // Logger
  private static final Logger logger = LoggerFactory.getLogger(SingletonUsingEnumTest.class);

  @AfterMethod
  public void cleanUp() {
    MyConfigManager.clearAll();
  }

  @Test(expectedExceptions = {IllegalArgumentException.class, ExceptionInInitializerError.class})
  public void nullMyConfig() {
    MyConfigManager.registerMyConfig(new MyConfig("", ""));
    final SingletonUsingEnum singleton = SingletonUsingEnum.INSTANCE;
  }

  @Test
  public void validMyConfig_nullConfigValue() {
    MyConfigManager.registerMyConfig(new MyConfig("", "b"));
    final SingletonUsingEnum singleton = SingletonUsingEnum.INSTANCE;
  }

  @Test(dependsOnMethods = {"nullMyConfig"})
  public void allValidData() {
    MyConfigManager.registerMyConfig(new MyConfig("a", "b"));
    final SingletonUsingEnum singleton = SingletonUsingEnum.INSTANCE;
  }

  @Configuration
  @EnableAspectJAutoProxy(proxyTargetClass = true)
  public static class SpringConfig {

  }
}

Result of the test execution is shown below. I have deliberately added expectedExceptions to the first test to hide the stack trace for the first test.

[TestNG] Running:
  /private/var/folders/l6/hmmqvjpj13ggmyc69sk1s5740000gn/T/testng-eclipse-135911498/testng-customsuite.xml

PASSED: nullMyConfig
FAILED: validMyConfig_nullConfigValue
java.lang.NoClassDefFoundError: Could not initialize class com.demo.singleton.SingletonUsingEnum
    at com.demo.singleton.SingletonUsingEnumTest.validMyConfig_nullConfigValue(SingletonUsingEnumTest.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:200)
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:157)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:212)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:707)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)

FAILED: allValidData
java.lang.NoClassDefFoundError: Could not initialize class com.demo.singleton.SingletonUsingEnum
    at com.demo.singleton.SingletonUsingEnumTest.allValidData(SingletonUsingEnumTest.java:44)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
    at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:200)
    at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.run(AbstractTestNGSpringContextTests.java:157)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:212)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:707)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
    at org.testng.TestRunner.privateRun(TestRunner.java:767)
    at org.testng.TestRunner.run(TestRunner.java:617)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
    at org.testng.SuiteRunner.run(SuiteRunner.java:240)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
    at org.testng.TestNG.run(TestNG.java:1057)
    at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
    at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
    at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)


===============================================
    Default test
    Tests run: 3, Failures: 2, Skips: 0
===============================================


===============================================
Default suite
Total tests run: 3, Failures: 2, Skips: 0
===============================================

[TestNG] Time taken by org.testng.reporters.JUnitReportReporter@6997f7f4: 8 ms
[TestNG] Time taken by org.testng.reporters.XMLReporter@4474c7fe: 14 ms
[TestNG] Time taken by [FailedReporter passed=0 failed=0 skipped=0]: 6 ms
[TestNG] Time taken by org.testng.reporters.SuiteHTMLReporter@395e7bc4: 14 ms
[TestNG] Time taken by org.testng.reporters.EmailableReporter2@71419cf7: 5 ms
[TestNG] Time taken by org.testng.reporters.jq.Main@6015eb5a: 31 ms

Appreciate if someone could help me explain what is going on.

Thanks, NN

Was it helpful?

Solution

Your test tries to use your singleton. So The JVM loads and initializes the enum. As part of this process, it calls your constructor to initialize the unique enum instance. The constructor throws an exception, preventing the class to be loaded correctly.

Once the JVM has tried and failed to load the class, it won't try loading it anymore.

Singleton is an anti-pattern. And doing that kind of process, leading to exceptions, in a singleton that is eagerly initialized when its class is loaded, is a really bad idea.

OTHER TIPS

The enum constant INSTANCE is being initialized statically when the class is loaded by the ClassLoader. When this fails in your first test nullConfig(), the class gets marked "erroneous" and static initialization isn't tried a second time.

As a general rule, the steps needed to initialize an enum or a singleton should be trivial and failsafe. Class loading an thus static initialization may occur in situations that you don't anticipate and should not trigger any application code to run.

If you need some complex logic to read configuration data to initialize your singleton, then maybe you should make that class a regular class that can be instantiated with different configurations as needed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top