Search code examples
asynchronousscalatestmockito-scala

Pattern for async testing with scalatest and mocking objects with mockito


I am writing unit tests for some async sections of my code (returning Futures) that also involves the need to mock a Scala object. Following these docs, I can successfully mock the object's functions. My question stems from the fact that withObjectMocked[FooObject.type] returns Unit, where async tests in scalatest require either an Assertion or Future[Assertion] to be returned. To get around this, I'm creating vars in my tests that I reassign within the function sent to withObjectMocked[FooObject.type], which ends up looking something like this:

class SomeTest extends AsyncWordSpec with Matchers with AsyncMockitoSugar with ResetMocksAfterEachAsyncTest {
      "wish i didn't need a temp var" in {
        var ret: Future[Assertion] = Future.failed(new Exception("this should be something")) // <-- note the need to create the temp var

        withObjectMocked[SomeObject.type] {
          when(SomeObject.someFunction(any)) thenReturn Left(Error("not found"))

          val mockDependency = mock[SomeDependency]
          val testClass = ClassBeingTested(mockDependency)

          ret = testClass.giveMeAFuture("test_id") map { r =>
              r should equal(Error("not found"))
          } // <-- set the real Future[Assertion] value here
        }

        ret // <-- finally, explicitly return the Future
      }
}

My question then is, is there a better/cleaner/more idiomatic way to write async tests that mock objects without the need to jump through this bit of a hoop? For some reason, I figured using AsyncMockitoSugar instead of MockitoSugar would have solved that for me, but withObjectMocked still returns Unit. Is this maybe a bug and/or a candidate for a feature request (the async version of withObjectMocked returning the value of the function block rather than Unit)? Or am I missing how to accomplish this sort of task?


Solution

  • You should refrain from using mockObject in a multi-thread environment as it doesn't play well with it. This is because the object code is stored as a singleton instance, so it's effectively global.

    When you use mockObject you're efectibly forcefully overriding this var (the code takes care of restoring the original, hence the syntax of usign it as a "resource" if you want).

    Because this var is global/shared, if you have multi-threaded tests you'll endup with random behaviour, this is the main reason why no async API is provided.

    In any case, this is a last resort tool, every time you find yourself using it you should stop and ask yourself if there isn't anything wrong with your code first, there are quite a few patterns to help you out here (like injecting the dependency), so you should rarely have to do this.