scalac finds wrong forAll method in ScalaTest test

1.7k views Asked by At

I have a ScalaTest 2 class that extends GeneratorDrivenPropertyChecks, and also indirectly extends FeatureSpec and Matchers (via a trait I wrote which extends those two classes). It has code like this in it:

forAll(mySequence) { myItem =>
  myItem.applicationID should be (foo.applicationID)
}

This fails to compile because scalac says:

[error] APISpec.scala:253: value applicationID is not a member of Seq[com.company.Item]
[error]          myItem.applicationID should be (foo.applicationID)
[error]                 ^

It turns out that, at least according to Eclipse Scala IDE, the compiler is resolving "forAll" as meaning this method, in GeneratorDrivenpropertyChecks:

  /**
   * Performs a property check by applying the specified property check function to arguments
   * supplied by the specified generators.
   *
   * <p>
   * Here's an example:
   * </p>
   *
   * <pre class="stHighlight">
   * import org.scalacheck.Gen
   *
   * // Define your own string generator:
   * val famousLastWords = for {
   *   s <- Gen.oneOf("the", "program", "compiles", "therefore", "it", "should", "work")
   * } yield s
   * 
   * forAll (famousLastWords) { (a: String) =>
   *   a.length should equal ((a).length)
   * }
   * </pre>
   *
   * @param fun the property check function to apply to the generated arguments
   */
  def forAll[A](genA: Gen[A], configParams: PropertyCheckConfigParam*)(fun: (A) => Unit)
    (implicit
      config: PropertyCheckConfig,
      shrA: Shrink[A]
    ) {
      // body omitted
  }

which is not the forAll method I'm wanting to use here!

Is this a bug in ScalaTest (i.e. that the two methods should not both be named forAll)?

And how should I call the correct method?

1

There are 1 answers

0
som-snytt On BEST ANSWER

Is this a bug in ScalaTest

It demonstrates the limits of method overloading.

In an article on selfless traits, Bill Venners describes this pattern as a workaround for these sorts of naming collisions.

In your case, one overload is preferred because it is defined in a "derived class".

(I think; I'm not a user of these test frameworks and one of the sources is generated, etc, so testing this was not a simple matter of firing up sbt and looking at code.)

(Edit: the scaladoc says you're supposed to import Inspectors._. Maybe you expected to inherit it with Matchers because it also suggests importing its companion, though that doesn't work for me offhandily. If you did import Inspectors._, you can't actually induce an overload by importing the name.)

(Edit: to explain the naming bit: see the beginning of Ch 2 of the spec, where it says that bindings of names have precedences, and names you inherit have higher precedence than names you import.)

Anyway, one solution is to remix-in Inspectors as shown below.

The other solution is to import the method with a rename:

import Inspectors.{ forAll => iforAll }

It's useful to try options "-Xprint:typer", "-Xlog-implicit-conversions" to see what's happening. In your case, your collection is promoted to a "constant gen" by an implicit view Gen.value in ScalaCheck 1.10.

import org.scalatest._
import org.scalatest.prop._

//class MySpec extends FeatureSpec with Matchers with GeneratorDrivenPropertyChecks

class MySpec extends FeatureSpec with Matchers with GeneratorDrivenPropertyChecks with Inspectors {
  case class Foo(id: Int)
  val items = 1 to 10 map (Foo.apply(_))
  forAll(items) { x => Console println x.id } 
}

object Test extends App {
  case class Foo(id: Int)
  val items = 1 to 10 map (Foo.apply(_))
  val sut = new MySpec
  sut.forAll(items) { x => Console println x.id }
  //sut.forAll[Foo](items) { x => Console println x.i }
}

Some debug output:

/*

[info] /home/apm/projects/skala-unit-tests/src/test/scala/maqi/MySpec.scala:15: inferred view from scala.collection.immutable.IndexedSeq[maqi.Test.Foo] to org.scalacheck.Gen[?] = scalacheck.this.Gen.value[scala.collection.immutable.IndexedSeq[maqi.Test.Foo]]:(x: scala.collection.immutable.IndexedSeq[maqi.Test.Foo])org.scalacheck.Gen[scala.collection.immutable.IndexedSeq[maqi.Test.Foo]]


> test
[info] Compiling 3 Scala sources to /home/apm/projects/skala-unit-tests/target/scala-2.10/test-classes...
[error] /home/apm/projects/skala-unit-tests/src/test/scala/maqi/MySpec.scala:16: overloaded method value forAll with alternatives:
[error]   (genAndNameA: (org.scalacheck.Gen[maqi.Test.Foo], String),configParams: maqi.Test.sut.PropertyCheckConfigParam*)(fun: maqi.Test.Foo => Unit)(implicit config: maqi.Test.sut.PropertyCheckConfig, implicit shrA: org.scalacheck.Shrink[maqi.Test.Foo])Unit <and>
[error]   (genA: org.scalacheck.Gen[maqi.Test.Foo],configParams: maqi.Test.sut.PropertyCheckConfigParam*)(fun: maqi.Test.Foo => Unit)(implicit config: maqi.Test.sut.PropertyCheckConfig, implicit shrA: org.scalacheck.Shrink[maqi.Test.Foo])Unit <and>
[error]   (nameA: String,configParams: maqi.Test.sut.PropertyCheckConfigParam*)(fun: maqi.Test.Foo => Unit)(implicit config: maqi.Test.sut.PropertyCheckConfig, implicit arbA: org.scalacheck.Arbitrary[maqi.Test.Foo], implicit shrA: org.scalacheck.Shrink[maqi.Test.Foo])Unit <and>
[error]   (fun: maqi.Test.Foo => Unit)(implicit config: maqi.Test.sut.PropertyCheckConfig, implicit arbA: org.scalacheck.Arbitrary[maqi.Test.Foo], implicit shrA: org.scalacheck.Shrink[maqi.Test.Foo])Unit
[error]  cannot be applied to (scala.collection.immutable.IndexedSeq[maqi.Test.Foo])
[error]   sut.forAll[Foo](items) { x => Console println x.i }
[error]             ^
*/