Sunday, August 9, 2015

Today I learned... A limitation of singletons in Scala

Singletons in Scala are pretty nice -- you can have the basic goodness of a Java singleton, but with none of the scaffolding. The static keyword is not available in Scala, so singletons are useful anytime you want a static type of behavior, but Scala singletons can extend abstract classes or traits, can be passed in, mocked, etc.

The limitation comes when you are doing unit testing and want to run multiple unit tests completely independently of each other. If your singleton has any sort of state associated with it -- not just a var or a mutable object in a val, but also something as innocuous as the current timestamp at initialization saved as a val, etc. -- then there is no way, with the possible exception of reflection, of resetting that singleton's state in-between unit tests.

These particular unit tests I'm writing are actually functional system tests -- I'm basically calling my main method with different command-line parameters once in each unit test. And since in the JVM world I can't just do something magical like shutdown and reload an AppDomain (I miss this concept from .NET), I'm stuck with state getting persisted between what are supposed to be multiple program runs, because they're all in the same JVM (of course, same as the unit test's JVM, because I'm calling them through the JVM and not through the command-line, so I can make sure they don't throw exceptions, verify side effects if desired, and so on). And the main culprit is the singleton object.

I wrote a trait that can be used to solve this problem, but unfortunately, it required refactoring all my usages of any singletons that had state, so instead of singleton.method(), it became singleton.instance.method(). Find/replace all solved this problem without any trouble for me.

The trait manages the singleton, and also requires anything extending it to have a reset method. I call the reset method for all stateful singletons in the finally block of my main method.

trait ResettableSingleton[T] {
  protected var instanceMaybe: Option[T] = None

  def isInstantiated: Boolean = instanceMaybe.nonEmpty

  /**    * Singleton instance    *    * @return Object of the class    */  def instance: T = {
    instanceMaybe synchronized {
      if (instanceMaybe.isEmpty) {
        instanceMaybe = Option(instantiate)
      }
      instanceMaybe.get
    }
  }

  /**    * Some might prefer shorter syntax, so this is just an alias for "instance"    *    * @return Instance    */  def get: T = instance

  /**    * Create an instance of the class    * This is abstract because there's no guarantee that every class has, e.g., a parameterless constructor.    *    * @return Object of the class    */  protected def instantiate: T
  /**    * Resets the singleton instance. Intended to be used if performing multiple test runs without restarting the JVM.    */  def reset(): Unit = {
    instanceMaybe = None
  }
}

Example usage:

// Take the original singleton, turn it into a class, and make the default constructor private.
class Blah private() {
  // ... Original singleton body goes here, and remains unchanged
}
/** * Resettable singleton of Blah */object Blah extends ResettableSingleton[Blah] {
/** * Create an instance of the class * @return Object of the class */ override def instantiate: Blah = {
new Blah
}
}

So I lose the syntactic sugar, but now my unit tests work. Hey, it could be worse... I could be writing Java.

No comments:

Post a Comment