Search code examples
scalascalatest

eventually doesn't attempt second assertion on first failed assertion


Below is a simplified code to understand how eventually works. But it only attempts assertion once, and eventually doesn't attempts second assertion.

package whisk_main

import org.scalatest.concurrent.Eventually.eventually
import org.scalatest.concurrent.Waiters.{interval, timeout}
import org.scalatest.flatspec.AsyncFlatSpecLike
import org.scalatest.matchers.should.Matchers
import org.scalatest.time.{Millis, Seconds, Span}

class EventuallySpec extends AsyncFlatSpecLike with Matchers{

  "eventually" should "work, as expected" in {

    val runnable = new Runnable {
      override def run(): Unit = {
        val currentThread = Thread.currentThread()
        println(s"$currentThread starts here")
        Thread.sleep(5000)
        println(s"$currentThread ends here")
      }
    }

    val threadThatSleepsForFiveSeconds = new Thread(runnable)

    threadThatSleepsForFiveSeconds.start()

    eventually(timeout(Span(15, Seconds)), interval(Span(2, Millis))) {
      println("asserting thread state")
      threadThatSleepsForFiveSeconds.getState should be(Thread.State.TERMINATED)
    }
  }
}

Below is the exception trace :-

Thread[Thread-1,5,main] starts here
asserting thread state





TIMED_WAITING was not equal to TERMINATED
ScalaTestFailureLocation: whisk_main.EventuallySpec at (EventuallySpec.scala:28)
Expected :TERMINATED
Actual   :TIMED_WAITING
<Click to see difference>

org.scalatest.exceptions.TestFailedException: TIMED_WAITING was not equal to TERMINATED
    at org.scalatest.matchers.MatchersHelper$.indicateFailure(MatchersHelper.scala:344)
    at org.scalatest.matchers.should.Matchers$ShouldMethodHelperClass.shouldMatcher(Matchers.scala:6778)
    at org.scalatest.matchers.should.Matchers$AnyShouldWrapper.should(Matchers.scala:6822)
    at whisk_main.EventuallySpec.$anonfun$new$2(EventuallySpec.scala:28)

I tried below example also that is documented on http://doc.scalatest.org/1.8/org/scalatest/concurrent/Eventually.html :-

val xs = 1 to 125
val it = xs.iterator
eventually { it.next should be (3) }

Above example also fails with below example :-

1 was not equal to 3
ScalaTestFailureLocation: whisk_main.EventuallyExample at (EventuallyExample.scala:12)
Expected :3
Actual   :1
<Click to see difference>

org.scalatest.exceptions.TestFailedException: 1 was not equal to 3
    at org.scalatest.matchers.MatchersHelper$.indicateFailure(MatchersHelper.scala:344)
    at org.scalatest.matchers.should.Matchers$ShouldMethodHelperClass.shouldMatcher(Matchers.scala:6778)
    at org.scalatest.matchers.should.Matchers$AnyShouldWrapper.should(Matchers.scala:6822)
    at whisk_main.EventuallyExample.$anonfun$new$2(EventuallyExample.scala:12)

Kindly suggest, why eventually is not working till it succeeds.


Solution

  • Your issue is that you are extending AsyncFlatSpecLike while your tests are synchronous. The behaviour you observe, is that AsyncFlatSpecLike takes the first Assertion of each of your tests, and fails upon it. Changing the class declaration into:

    class EventuallySpec extends AnyFlatSpecLike with Matchers {
    

    will keep the tests synchronous, and make your tests pass, with the following output:

    Thread[Thread-0,5,main] starts here
    asserting thread state
    asserting thread state
    .
    .
    .
    asserting thread state
    asserting thread state
    Thread[Thread-0,5,main] ends here
    asserting thread state
    

    as you'd expect.

    Another option you have, is using AsyncFlatSpecLike if your test is really async. For example the following test passes as well:

    class MySpec extends AsyncFlatSpecLike with Matchers {
    
      "eventually" should "work, as expected" in {
    
        val runnable = new Runnable {
          override def run(): Unit = {
            val currentThread = Thread.currentThread()
            println(s"$currentThread starts here")
            Thread.sleep(5000)
            println(s"$currentThread ends here")
          }
        }
    
        val threadThatSleepsForFiveSeconds = new Thread(runnable)
    
        threadThatSleepsForFiveSeconds.start()
    
        eventually(timeout(Span(15, Seconds)), interval(Span(2, Millis))) {
          println("asserting thread state")
          Future {
            Thread.sleep(10)
            threadThatSleepsForFiveSeconds.getState must be(Thread.State.TERMINATED)
          }
        }
      }
    }