Scala avoid Thread sleep for asynchronous test cases

Viewed 339

I'm writing unit tests for a method that runs a primary function and then logs details about the request asynchronously.

def someMethod(data: Object): SomeType =
  for {
    result <- someOtherMethod(data).withLogging(logMethod)
  }

The method logMethod is an async task. In my tests, I want to ensure that the logger is receiving a message, however, the thread completes too soon at times making the test flaky and causing an Unsatisfied result sometimes.

Example test case:

it("logs an error if an upload was attempted with some failure case") {
  val uploadData = someData

  mockSomeCall()

  mockAnotherCall()

  testController.methodWeAreTesting(uploadData).shouldBeRight()

  Thread.sleep(75)

  (stubLogger
    .warn(_: RichMsg, _: Throwable, _: AnyId)(_: () => SourceLocation))
    .verify(where { (msg: RichMsg, _, _, _) =>
      msg.toString.equals(s"Some specific message")
     })
}

I'm not enjoying adding a Thread.sleep every time I need to ensure that the logger is receiving a specific message. I'd like to be able to possibly wrap the stubLogger expectation.

Let me know if more information is needed.

1 Answers

I think promise is what you need to add in logMethod. As per the documentation:

While futures are defined as a type of read-only placeholder object created for a result which doesn’t yet exist, a promise can be thought of as a writable, single-assignment container, which completes a future. That is, a promise can be used to successfully complete a future with a value (by “completing” the promise) using the success method. Conversely, a promise can also be used to complete a future with an exception, by failing the promise, using the failure method.

A promise p completes the future returned by p.future. This future is specific to the promise p. Depending on the implementation, it may be the case that p.future eq p.

In the tests, once you get back the result, you can compare the result with the message that you are trying to compare.

A sample code is shown below:

object Test1 extends App {
  import scala.concurrent.{Future, Promise}
  import scala.concurrent.ExecutionContext.Implicits.global
  import scala.util.{Success, Failure}

  var promiseMessage: String = _
  val promise = Promise[String] //The promise will hold a string

  //A future tells the System to spawn a new thread and run the code block inside it
  val logMethod =  (elem: String) => Future{
    promise.success(elem)
    elem
  }

  def method(data: String): Future[String] = {
    for {
      result <- logMethod(data)
    } yield result
  }

  val f1 = method("Hi!! I love scala async programming")

  promise completeWith f1
  val promiseFuture = promise.future

  promiseFuture onComplete {
    case Success(value) =>
      promiseMessage = value
      println(promiseMessage)
    case Failure(ex) => println(ex)
  }

  Await.result(promiseFuture, 100 seconds)

  if (promiseMessage == "Hi!! I love scala async programming") println("correct")
}

In the code, promise is a Promise object which promises a string when the future gets complete. All you need to do is complete the promise with future as shown in: promise completeWith f1 and then, add a handler as to what to do when it is successful or failure using promiseFuture onComplete.

If you want to check if logging happened you can await on the promiseFuture or else continue with the further process and when the logging is done, the promise will print that is successful as shown in the code.

Let me know it helps!!

Related