2
votes

So I have some server code with an endpoint, where I want to run stuff asynchronously. To that end I'm using futures. There are tasks that lead to the result, but there are also expensive IO tasks that don't, so I perform them in a fire and forget Future like so:

import scala.concurrent.Future

val ec = scala.concurrent.ExecutionContext.global

Future {
  Thread.sleep(10000)
  println("Done")
}(ec)

println("Exiting now")

Output of that is:

import scala.concurrent.Future

ec: scala.concurrent.ExecutionContextExecutor = scala.concurrent.impl.ExecutionContextImpl@2315ca48

res0: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@7d1e50cb

Exiting now
res1: Unit = ()

Normally on the server this is not that big of a problem, because the context keeps running as it receives other tasks and so the IO can finish. But when I test, the test finishes and the tasks inside start throwing, because the services they use become unavailable.

So the question is: How do I wait for these fire and forget futures without also blocking my main execution?

1
You definitely have to block the main execution.Nyavro
Can you paste the test code as well.curious
@Nyavro I really really do not want to do that. Errors are logged on the server and they do not need to be propagated to the client. We are talking about adding 3-5 seconds to the response time if I wait for the IO.mirosval
I'd like to tell the execution context to wait for the tasks its runningmirosval
Which testing framework do you use?Sergey

1 Answers

4
votes

To illustrate my answer, let's imagine you access some fileService to load the file in a fire-and-forget section, and you use Mockito (or other mock framework, the idea remains the same) for your unit tests. Then, the code I'll be referring to in my explanation would look something like that:

class SystemUnderTest(val fileService: MyFileService) {
  def testedMethod(): GoodResult = {
    val file = // ... some logic to extract file ...
    Future {
      fileService.uploadFile(file)
    }
    // ... other logic returning an instance of GoodResult
  }
}

Then, in your test mix in org.scalatest.Eventually and make an assertion like this:

eventually {
  verify(fileService).uploadFile(any())
}

It will make sure the main process doesn't quit before it can verify the uploader service has been called (or the timeout defined by implicit patienceConfig has expired).


And, of course, you always have the option to return the Future you don't wait for in your production code and await it in tests.