What is the best way to assert for a scala method

2019-08-25 02:17发布

问题:

I have a method. This method may return Future.failed(.....) or Future.successful(()).

def calculate(x: Int, y: Int): Future[Unit] = { ........ }

Now I need to test this method. What is the best way to assert for the test which verifies Future.successful(()) case .

回答1:

Scalatest offers a few ways of working with Futures.

Option 1: isReadyWithin

import org.scalatest.concurrent.ScalaFutures._
import scala.concurrent.duration._

calculate(1, 3).isReadyWithin(1.second) should be(true)

If you wanted to do something with some return value here, you could use whenReady:

implicit val patienceConfig = PatienceConfig(1.second)

def calculateWithResult(i: Int, j: Int): Future[Int] = ???

whenReady(calculateWithResult(1,3)) { result =>
    result should be(4)
}

You need an implicit PatienceConfig in scope that tells whenReady when to fail the test because of a timeout. I believe there's a default one in one of the scalatest libraries but the time period chosen is quite short - something on the order of 10 milliseconds - and can often cause flaky tests.

Option 2: AsyncXSpec

There are Async varieties of the FlatSpec, FreeSpec, FunSpec, etc. traits. They work much as their synchronous varieties except that any test must now return a value of type Future[Assertion]. For instance:

class Test extends AsyncFreeSpec {
  "An asynchronous method" - {
    "should succeed" in {
      calculate(1,3).map { _ =>
        // just want to make sure the future completes
        succeed
      }
    }
  }
}

Again, you can run a test against the result here. Note that this variant means that every test in your test class must return a Future, so it's not great if you want to mix synchronous and asynchronous tests. I'm also honestly not sure how AsyncXSpec chooses its timeout value.

Option Don't: Await.result

I would recommend against using Await.result as it blocks the thread for the duration. To my knowledge, the above two options are designed so asynchronous tests can be easily run in parallel.

Caveats:

You want to be super careful with your timeouts when doing asynchronous testing. Too long, and your tests can end up hanging for ages if something goes wrong. Too short, and your tests will be flaky. And the program may perform differently in different environments, so you might find that a timeout that's perfectly sufficient on your local machine has the tests on the build server failing 5% of the time. Be careful!



回答2:

At least two options:

  • You can either Await.result and then make sure that the result is what you expected by asserting something about it.
  • You can make your assertions inside the future itself, but then use something like AsyncFlatSpec for your tests.


回答3:

I assume you already extend ScalaFutures in the test class and you have configured the timeout(patienceConfig) as per your needs. If you have done that, then you could use one of the below approaches:

whenReady(calculate(1,3))(_ => succeed)

or

whenReady(calculate(1,3))(_ shouldBe ())

The first approach is preferable as there cannot be any other value for Unit, so we don't have to explicitly check for it, unless the code returns null for some reason.