Search code examples
scalascalatestscalamock

Mock a function call in scala for unittesting


I would like to write a unittest to the function

import com.github.nscala_time.time.Imports.{DateTime, richReadableInstant}

def myFunction(ts: Long):Long = {
  (new DateTime(ts) to DateTime.now()).toDurationMillis
}

Is it possible to somehow mock Datetime.now(), so that the test result does not depend on when the test is running?


Solution

  • From what I can see this library is a wrapper around joda-time (which, as the official documentation recommends, should be dropped in favor of java.time, but I assume you have some constraint that forces you to work on a pre-Java 8 release).

    joda-time comes with a collection of static helpers that, among other things, allow you to manage what the response is when a method asks for the "current time" (see their JavaDoc here).

    The easiest possible way (but possibly prone to mistakes due to the shared mutable state it relies on) would look like the following:

    import com.github.nscala_time.time.Imports.{DateTime, richReadableInstant}
    import org.joda.time.DateTimeUtils
    
    DateTimeUtils.setCurrentMillisFixed(42)
    
    def myFunction(ts: Long):Long = {
      (new DateTime(ts) to DateTime.now()).toDurationMillis
    }
    
    assert(myFunction(42) == 0)
    

    You can play around with this code here on Scastie.

    As mentioned, this approach is a bit clunky and relies on shared mutable state, which makes it prone to confusing errors. You can build a nice small helper to make sure you can use a custom clock on a specific test and reset to the system clock once done. The required synchronization means a performance hit, but it's probably acceptable for your tests.

    import com.github.nscala_time.time.Imports.{DateTime, richReadableInstant}
    import org.joda.time.DateTimeUtils
    import org.joda.time.DateTimeUtils.MillisProvider
    
    def myFunction(ts: Long):Long = {
      (new DateTime(ts) to DateTime.now()).toDurationMillis
    }
    
    final class FixedClock(at: Long) extends MillisProvider {
      override def getMillis(): Long = at
    }
    
    def withCustomClock[A](clock: MillisProvider)(f: => A): A = {
      synchronized {
        try {
          DateTimeUtils.setCurrentMillisProvider(clock)
          f
        } finally {
          DateTimeUtils.setCurrentMillisSystem() // _always_ reset to the system clock once done
        }
      }
    }
    
    assert(myFunction(42) > 1000000)
    
    withCustomClock(new FixedClock(at = 42)) {
      assert(myFunction(42) == 0)
      Thread.sleep(1000)
      assert(myFunction(42) == 0)
    }
    
    assert(myFunction(42) > 1000000)
    

    You can play around with this other example here on this other worksheet on Scastie.