paulbutcher / ScalaMock

Native Scala mocking framework
http://scalamock.org/
MIT License
502 stars 99 forks source link

ScalaMock macro does not infer/resolve context-bound type-parameters correctly #445

Open DarrenBishop opened 2 years ago

DarrenBishop commented 2 years ago

ScalaMock Version (e.g. 3.5.0)

5.2.0

Scala Version (e.g. 2.12)

2.13.8

Runtime (JVM or JS)

JVM

Please describe the expected behavior of the issue

I expect the mock/stub to be generated successfully i.e. no compilation error

Please provide a description of what actually happens

SBT tells it best:

sbt:example-project> testOnly *MinimalScalaMockSpec

...

[info] compiling 1 Scala source to /path/to/example-project/target/scala-2.13/test-classes ...
[error] [E1] src/test/scala/reproducible/example/MinimalScalaMockSpec.scala
[error]      type mismatch;
[error]       found   : reproducible.example.SomeTypeClass[A]
[error]       required: reproducible.example.SomeTypeClass[Int]
[error]      L36:     val dep = stub[ConcreteDep[Int]]
[error]                             ^
[error] src/test/scala/reproducible/example/MinimalScalaMockSpec.scala: L36 [E1]
[info] Legend: Ln = line n, Cn = column n, En = error n
[error] (Test / compileIncremental) Compilation failed
[error] Total time: 0 s, completed 17 May 2022, 11:55:30
sbt:example-project>

Reproducible Test Case

package reproducible.test.case

import org.scalamock.scalatest.MockFactory
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

trait SomeTypeClass[A0] {
  type A = A0
}

trait SomeDependencyWithTypeParameters[A0, B0, C0[_]] {
  type A = A0
  type B = B0
  type C[X] = C0[X]

  def getStatus: String
}

class ConcreteDep[A: SomeTypeClass] extends SomeDependencyWithTypeParameters[A, String, List] {
  def getStatus: String = "pass"
}

class Composite[A, B, C[_], D0 <: SomeDependencyWithTypeParameters[A, B, C]](
    dep: D0
) {
  type D = D0
  def doSomething(): String = dep.getStatus
}

class MinimalScalaMockSpec extends AnyFlatSpec with Matchers with MockFactory {

  "simply" should "mock a dependency with type parameters" in {

    type AlternateDep = SomeDependencyWithTypeParameters[Double, Boolean, Option]

    val dep = stub[ConcreteDep[Int]]
    (() => dep.getStatus).expects().returning { "pass" }.once()
    val alt = mock[AlternateDep]
    (() => alt.getStatus).expects().returning { "undecided" }.twice()

    val first = new Composite[Int, String, List, ConcreteDep[Int]](dep)
    val second = new Composite[Double, Boolean, Option, AlternateDep](alt)
    first.doSomething() should be(second.doSomething())
  }
}