Skip to content

Commit fb3bc4e

Browse files
authored
Merge pull request #79 from mockito/fix-spy-creation
Fix spy creation
2 parents 1fb404e + 54932a8 commit fb3bc4e

File tree

9 files changed

+163
-48
lines changed

9 files changed

+163
-48
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The library has independent developers, release cycle and versioning from core m
2222

2323
* Artifact identifier: "org.mockito:mockito-scala_2.11:VERSION"
2424
* Artifact identifier: "org.mockito:mockito-scala_2.12:VERSION"
25+
* Artifact identifier: "org.mockito:mockito-scala_2.13.0-M2:VERSION"
2526
* Latest version - see [release notes](/docs/release-notes.md)
2627
* Repositories: [Maven Central](https://search.maven.org/search?q=mockito-scala) or [JFrog's Bintray](https://bintray.com/mockito/maven/mockito-scala)
2728

common/src/main/scala/org/mockito/MockitoAPI.scala

+9-8
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress
1919
import org.mockito.internal.util.reflection.LenientCopyTool
2020
import org.mockito.invocation.InvocationOnMock
2121
import org.mockito.mock.MockCreationSettings
22-
import org.mockito.stubbing.{ Answer, DefaultAnswer, ScalaFirstStubbing, Stubber }
23-
import org.mockito.verification.{ VerificationMode, VerificationWithTimeout }
22+
import org.mockito.stubbing.{Answer, DefaultAnswer, ScalaFirstStubbing, Stubber}
23+
import org.mockito.verification.{VerificationMode, VerificationWithTimeout}
2424

2525
import scala.collection.JavaConverters._
2626
import scala.reflect.ClassTag
@@ -174,17 +174,18 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
174174
override def mock[T <: AnyRef: ClassTag: WeakTypeTag](mockSettings: MockSettings): T = {
175175
val interfaces = ReflectionUtils.interfaces
176176

177-
mockSettings match {
178-
case m: MockSettingsImpl[_] =>
179-
require(m.getExtraInterfaces.isEmpty, "If you want to add extra traits to the mock use the syntax mock[MyClass with MyTrait]")
180-
case _ =>
177+
val realClass: Class[T] = mockSettings match {
178+
case m: MockSettingsImpl[_] if !m.getExtraInterfaces.isEmpty =>
179+
throw new IllegalArgumentException("If you want to add extra traits to the mock use the syntax mock[MyClass with MyTrait]")
180+
case m: MockSettingsImpl[_] if m.getSpiedInstance != null => m.getSpiedInstance.getClass.asInstanceOf[Class[T]]
181+
case _ => clazz
181182
}
182183

183184
val settings =
184185
if (interfaces.nonEmpty) mockSettings.extraInterfaces(interfaces: _*)
185186
else mockSettings
186187

187-
ReflectionUtils.markMethodsWithLazyArgs(clazz)
188+
ReflectionUtils.markMethodsWithLazyArgs(realClass)
188189

189190
def createMock(settings: MockCreationSettings[T]): T = {
190191
val mock = getMockMaker.createMock(settings, ScalaMockHandler(settings))
@@ -195,7 +196,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
195196

196197
settings match {
197198
case s: MockSettingsImpl[_] =>
198-
val creationSettings = s.build[T](clazz)
199+
val creationSettings = s.build[T](realClass)
199200
val mock = createMock(creationSettings)
200201
mockingProgress.mockingStarted(mock, creationSettings)
201202
mock

common/src/main/scala/org/mockito/ReflectionUtils.scala

+9-3
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ private[mockito] object ReflectionUtils {
1818

1919
implicit class InvocationOnMockOps(invocation: InvocationOnMock) {
2020
def returnType: Class[_] = {
21-
val method = invocation.getMethod
22-
val clazz = method.getDeclaringClass
21+
val method = invocation.getMethod
22+
val clazz = method.getDeclaringClass
2323
val javaReturnType = invocation.getMethod.getReturnType
2424

2525
if (javaReturnType == classOf[Object])
@@ -32,9 +32,15 @@ private[mockito] object ReflectionUtils {
3232
.map(_.asMethod)
3333
.filter(_.returnType.typeSymbol.isClass)
3434
.map(methodSymbol => mirror.runtimeClass(methodSymbol.returnType.typeSymbol.asClass))
35-
.getOrElse(GenericsResolver.resolve(invocation.getMock.getClass).`type`(clazz).method(method).resolveReturnClass())
35+
.orElse(resolveGenerics)
36+
.getOrElse(javaReturnType)
3637
else javaReturnType
3738
}
39+
40+
private def resolveGenerics: Option[Class[_]] =
41+
scala.util.Try {
42+
GenericsResolver.resolve(invocation.getMock.getClass).`type`(clazz).method(invocation.getMethod).resolveReturnClass()
43+
}.toOption
3844
}
3945

4046
def interfaces[T](implicit tag: WeakTypeTag[T]): List[Class[_]] =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock-maker-inline
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,50 @@
11
package user.org.mockito
22

3-
import org.mockito.{ArgumentMatchersSugar, MockitoSugar}
4-
import org.scalatest.{Matchers, WordSpec}
3+
import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
4+
import org.scalatest.prop.TableDrivenPropertyChecks
5+
import org.scalatest.{ Matchers, WordSpec }
56

67
//noinspection RedundantDefaultArgument
7-
class MockitoSugarTest_212 extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar {
8+
class MockitoSugarTest_212 extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar with TableDrivenPropertyChecks {
89

9-
class Foo {
10-
def bar = "not mocked"
10+
val scenarios = Table(
11+
("testDouble", "foo", "baz"),
12+
("mock", () => mock[Foo], () => mock[Baz]),
13+
("spy", () => spy(new Foo), () => spy(new ConcreteBaz))
14+
)
1115

12-
def iHaveByNameArgs(normal: String, byName: => String, byName2: => String): String = ???
16+
forAll(scenarios) { (testDouble, foo, baz) =>
17+
testDouble should {
1318

14-
def iHaveByNameAndFunction0Args(normal: String, f0: () => String, byName: => String): String = ???
15-
}
16-
17-
trait Baz {
18-
def traitMethod(defaultArg: Int = 30, anotherDefault: String = "hola"): Int = ???
19-
}
20-
21-
class SomeClass extends Foo with Baz
19+
"work with default arguments in traits" in {
20+
val testDouble = baz()
2221

23-
"mock[T]" should {
22+
when(testDouble.traitMethodWithDefaultArgs(any, any)) thenReturn 69
2423

25-
"work with default arguments in traits" in {
26-
val aMock = mock[Foo with Baz]
24+
testDouble.traitMethodWithDefaultArgs() shouldBe 69
2725

28-
when(aMock.bar) thenReturn "mocked!"
29-
when(aMock.traitMethod(any, any)) thenReturn 69
26+
verify(testDouble).traitMethodWithDefaultArgs(30, "hola")
27+
}
3028

31-
aMock.bar shouldBe "mocked!"
32-
aMock.traitMethod() shouldBe 69
29+
"work with by-name arguments and matchers (by-name arguments have to be the last ones when using matchers)" in {
30+
val testDouble = foo()
3331

34-
verify(aMock).traitMethod(30, "hola")
35-
}
36-
37-
"work with by-name arguments and matchers (by-name arguments have to be the last ones when using matchers)" in {
38-
val aMock = mock[Foo]
32+
when(testDouble.iHaveByNameArgs(any, any, any)) thenReturn "mocked!"
3933

40-
when(aMock.iHaveByNameArgs(any, any, any)) thenReturn "mocked!"
34+
testDouble.iHaveByNameArgs("arg1", "arg2", "arg3") shouldBe "mocked!"
4135

42-
aMock.iHaveByNameArgs("arg1", "arg2", "arg3") shouldBe "mocked!"
43-
44-
verify(aMock).iHaveByNameArgs(eqTo("arg1"), endsWith("g2"), eqTo("arg3"))
45-
}
36+
verify(testDouble).iHaveByNameArgs(eqTo("arg1"), endsWith("g2"), eqTo("arg3"))
37+
}
4638

47-
"work with by-name and Function0 arguments (by-name arguments have to be the last ones when using matchers)" in {
48-
val aMock = mock[Foo]
39+
"work with by-name and Function0 arguments (by-name arguments have to be the last ones when using matchers)" in {
40+
val testDouble = foo()
4941

50-
when(aMock.iHaveByNameAndFunction0Args(any, any, any)) thenReturn "mocked!"
42+
when(testDouble.iHaveByNameAndFunction0Args(any, any, any)) thenReturn "mocked!"
5143

52-
aMock.iHaveByNameAndFunction0Args("arg1", () => "arg2", "arg3") shouldBe "mocked!"
44+
testDouble.iHaveByNameAndFunction0Args("arg1", () => "arg2", "arg3") shouldBe "mocked!"
5345

54-
verify(aMock).iHaveByNameAndFunction0Args(eqTo("arg1"), function0("arg2"), startsWith("arg"))
46+
verify(testDouble).iHaveByNameAndFunction0Args(eqTo("arg1"), function0("arg2"), startsWith("arg"))
47+
}
5548
}
5649
}
5750
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package user.org.mockito
2+
3+
import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito}
4+
import org.scalatest
5+
import org.scalatest.WordSpec
6+
import user.org.mockito.matchers.{ValueCaseClass, ValueClass}
7+
8+
class IdiomaticMockitoTest_213 extends WordSpec with scalatest.Matchers with IdiomaticMockito with ArgumentMatchersSugar {
9+
10+
class Foo {
11+
def valueClass(n: Int, v: ValueClass): String = ???
12+
13+
def valueCaseClass(n: Int, v: ValueCaseClass): String = ???
14+
}
15+
16+
"value class matchers" should {
17+
18+
"eqTo macro works with new syntax" in {
19+
val aMock = mock[Foo]
20+
21+
aMock.valueClass(1, eqTo(new ValueClass("meh"))) shouldReturn "mocked!"
22+
aMock.valueClass(1, new ValueClass("meh")) shouldBe "mocked!"
23+
aMock.valueClass(1, eqTo(new ValueClass("meh"))) was called
24+
25+
aMock.valueClass(*, new ValueClass("moo")) shouldReturn "mocked!"
26+
aMock.valueClass(11, new ValueClass("moo")) shouldBe "mocked!"
27+
aMock.valueClass(*, new ValueClass("moo")) was called
28+
29+
val valueClass = new ValueClass("blah")
30+
aMock.valueClass(1, eqTo(valueClass)) shouldReturn "mocked!"
31+
aMock.valueClass(1, valueClass) shouldBe "mocked!"
32+
aMock.valueClass(1, eqTo(valueClass)) was called
33+
34+
aMock.valueCaseClass(2, eqTo(ValueCaseClass(100))) shouldReturn "mocked!"
35+
aMock.valueCaseClass(2, ValueCaseClass(100)) shouldBe "mocked!"
36+
aMock.valueCaseClass(2, eqTo(ValueCaseClass(100))) was called
37+
38+
val caseClassValue = ValueCaseClass(100)
39+
aMock.valueCaseClass(3, eqTo(caseClassValue)) shouldReturn "mocked!"
40+
aMock.valueCaseClass(3, caseClassValue) shouldBe "mocked!"
41+
aMock.valueCaseClass(3, eqTo(caseClassValue)) was called
42+
43+
aMock.valueCaseClass(*, ValueCaseClass(200)) shouldReturn "mocked!"
44+
aMock.valueCaseClass(4, ValueCaseClass(200)) shouldBe "mocked!"
45+
aMock.valueCaseClass(*, ValueCaseClass(200)) was called
46+
}
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package user.org.mockito
2+
3+
import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
4+
import org.scalatest.prop.TableDrivenPropertyChecks
5+
import org.scalatest.{ Matchers, WordSpec }
6+
7+
//noinspection RedundantDefaultArgument
8+
class MockitoSugarTest_213 extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar with TableDrivenPropertyChecks {
9+
10+
val scenarios = Table(
11+
("testDouble", "foo", "baz"),
12+
("mock", () => mock[Foo], () => mock[Baz]),
13+
("spy", () => spy(new Foo), () => spy(new ConcreteBaz))
14+
)
15+
16+
forAll(scenarios) { (testDouble, foo, baz) =>
17+
testDouble should {
18+
19+
"work with default arguments in traits" in {
20+
val testDouble = baz()
21+
22+
when(testDouble.traitMethodWithDefaultArgs(any, any)) thenReturn 69
23+
24+
testDouble.traitMethodWithDefaultArgs() shouldBe 69
25+
26+
verify(testDouble).traitMethodWithDefaultArgs(30, "hola")
27+
}
28+
29+
"work with by-name arguments and matchers (by-name arguments have to be the last ones when using matchers)" in {
30+
val testDouble = foo()
31+
32+
when(testDouble.iHaveByNameArgs(any, any, any)) thenReturn "mocked!"
33+
34+
testDouble.iHaveByNameArgs("arg1", "arg2", "arg3") shouldBe "mocked!"
35+
36+
verify(testDouble).iHaveByNameArgs(eqTo("arg1"), endsWith("g2"), eqTo("arg3"))
37+
}
38+
39+
"work with by-name and Function0 arguments (by-name arguments have to be the last ones when using matchers)" in {
40+
val testDouble = foo()
41+
42+
when(testDouble.iHaveByNameAndFunction0Args(any, any, any)) thenReturn "mocked!"
43+
44+
testDouble.iHaveByNameAndFunction0Args("arg1", () => "arg2", "arg3") shouldBe "mocked!"
45+
46+
verify(testDouble).iHaveByNameAndFunction0Args(eqTo("arg1"), function0("arg2"), startsWith("arg"))
47+
}
48+
}
49+
}
50+
}

core/src/test/scala/user/org/mockito/IdiomaticMockitoTest.scala

+10
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,16 @@ class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito
555555
}
556556
}
557557

558+
"spy" should {
559+
"interact correctly with the real object" in {
560+
val it = spy(Iterator.continually("hello"))
561+
val result = it.map(_.length)
562+
it.next() wasNever called
563+
result.next() shouldBe 5
564+
it.next() wasCalled once
565+
}
566+
}
567+
558568
"doStub" should {
559569
"stub a spy that would fail if the real impl is called" in {
560570
val aSpy = spy(new Org)

core/src/test/scala/user/org/mockito/TestModel.scala

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package user.org.mockito
2-
import user.org.mockito.matchers.{ ValueCaseClass, ValueClass }
2+
import user.org.mockito.matchers.{ValueCaseClass, ValueClass}
33

44
class Foo {
55
def bar = "not mocked"
66

7+
def iHaveByNameArgs(normal: String, byName: => String, byName2: => String): String = "not mocked"
8+
79
def iHaveSomeDefaultArguments(noDefault: String, default: String = "default value"): String = "not mocked"
810

911
def iStartWithByNameArgs(byName: => String, normal: String): String = "not mocked"
@@ -12,6 +14,8 @@ class Foo {
1214

1315
def iHaveFunction0Args(normal: String, f0: () => String): String = "not mocked"
1416

17+
def iHaveByNameAndFunction0Args(normal: String, f0: () => String, byName: => String): String = "not mocked"
18+
1519
def returnBar: Bar = new Bar
1620

1721
def doSomethingWithThisIntAndString(v: Int, v2: String): ValueCaseClass = ValueCaseClass(v)
@@ -28,6 +32,7 @@ class Bar {
2832

2933
trait Baz {
3034
def traitMethod(arg: Int): ValueCaseClass = ValueCaseClass(arg)
35+
def traitMethodWithDefaultArgs(defaultArg: Int = 30, anotherDefault: String = "hola"): Int = -1
3136
}
3237

3338
class ConcreteBaz extends Baz

0 commit comments

Comments
 (0)