Search code examples
scalaunit-testingasynchronousscalatest

How to use fixture-context objects with async specs in ScalaTest?


I'm trying to use fixture-context objects with async testing in ScalaTest.

The naive approach of simply combining the two doesn't compile. For example:

import org.scalatest.AsyncWordSpec

import scala.collection.GenTraversableOnce
import scala.concurrent.{ExecutionContext, Future}
import scala.math.Numeric.IntIsIntegral

trait Adder[T] {
  implicit def num: Numeric[T]
  def add(number: T): Unit
  def result: Future[T]
}

object Foo {
  def doubleSum[T](adder: Adder[T], numbers: GenTraversableOnce[T])(implicit ec: ExecutionContext): Future[T] = {
    numbers.foreach(adder.add)
    val num = adder.num
    import num._
    adder.result.map(result => result + result)
  }
}

class FooSpec extends AsyncWordSpec {

  trait IntAdder {
    val adder = new Adder[Int] {
      override implicit val num = IntIsIntegral
      private var sum = Future.successful(num.zero)
      override def add(number: Int): Unit = sum = sum.map(_ + number)
      override def result: Future[Int] = sum
    }
  }

  "Testing" should {
    "be productive" in new IntAdder {
      Foo.doubleSum(adder, Seq(1, 2, 3)).map(sum => assert(sum == 12))
    }
  }
}

This fails to compile with:

Error:(37, 11) type mismatch;
found   : FooSpec.this.IntAdder
required: scala.concurrent.Future[org.scalatest.compatible.Assertion]
          new IntAdder {

This is a legitimate error but I'm wondering what ways there are of solving this in a ScalaTest style.

I want to keep the fixture-context object since that allows me to use the stackable trait pattern.


Solution

  • What about:

    import org.scalatest.compatible.Assertion
    
    class FooSpec extends AsyncWordSpec {
    
      def withIntAdder(test: Adder[Int] => Future[Assertion]): Future[Assertion] = {
         val adder = new Adder[Int] { ... }
         test(adder)
      }
    
      "Testing" should {
        "be productive" in withIntAdder { adder =>
          Foo.doubleSum(adder, Seq(1, 2, 3)).map(sum => assert(sum == 12))
        }
      }
    }
    

    Or

    class FooSpec extends AsyncWordSpec {
    
      trait IntAdder {
        val adder = new Adder[Int] {
          override implicit val num = IntIsIntegral
          private var sum = Future.successful(num.zero)
          override def add(number: Int): Unit = sum = sum.map(_ + number)
          override def result: Future[Int] = sum
        }
      }
      trait SomeMoreFixture {
    
      }
    
      "Testing" should {
        "be productive" in {
          val fixture = new IntAdder with SomeMoreFixture
          import fixture._
          Foo.doubleSum(adder, Seq(1, 2, 3)).map(sum => assert(sum == 12))
        }
      }
    }