Skip to content

Fix spy creation #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The library has independent developers, release cycle and versioning from core m

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

Expand Down
17 changes: 9 additions & 8 deletions common/src/main/scala/org/mockito/MockitoAPI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress
import org.mockito.internal.util.reflection.LenientCopyTool
import org.mockito.invocation.InvocationOnMock
import org.mockito.mock.MockCreationSettings
import org.mockito.stubbing.{ Answer, DefaultAnswer, ScalaFirstStubbing, Stubber }
import org.mockito.verification.{ VerificationMode, VerificationWithTimeout }
import org.mockito.stubbing.{Answer, DefaultAnswer, ScalaFirstStubbing, Stubber}
import org.mockito.verification.{VerificationMode, VerificationWithTimeout}

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

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

val settings =
if (interfaces.nonEmpty) mockSettings.extraInterfaces(interfaces: _*)
else mockSettings

ReflectionUtils.markMethodsWithLazyArgs(clazz)
ReflectionUtils.markMethodsWithLazyArgs(realClass)

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

settings match {
case s: MockSettingsImpl[_] =>
val creationSettings = s.build[T](clazz)
val creationSettings = s.build[T](realClass)
val mock = createMock(creationSettings)
mockingProgress.mockingStarted(mock, creationSettings)
mock
Expand Down
12 changes: 9 additions & 3 deletions common/src/main/scala/org/mockito/ReflectionUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ private[mockito] object ReflectionUtils {

implicit class InvocationOnMockOps(invocation: InvocationOnMock) {
def returnType: Class[_] = {
val method = invocation.getMethod
val clazz = method.getDeclaringClass
val method = invocation.getMethod
val clazz = method.getDeclaringClass
val javaReturnType = invocation.getMethod.getReturnType

if (javaReturnType == classOf[Object])
Expand All @@ -32,9 +32,15 @@ private[mockito] object ReflectionUtils {
.map(_.asMethod)
.filter(_.returnType.typeSymbol.isClass)
.map(methodSymbol => mirror.runtimeClass(methodSymbol.returnType.typeSymbol.asClass))
.getOrElse(GenericsResolver.resolve(invocation.getMock.getClass).`type`(clazz).method(method).resolveReturnClass())
.orElse(resolveGenerics)
.getOrElse(javaReturnType)
else javaReturnType
}

private def resolveGenerics: Option[Class[_]] =
scala.util.Try {
GenericsResolver.resolve(invocation.getMock.getClass).`type`(clazz).method(invocation.getMethod).resolveReturnClass()
}.toOption
}

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

import org.mockito.{ArgumentMatchersSugar, MockitoSugar}
import org.scalatest.{Matchers, WordSpec}
import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.{ Matchers, WordSpec }

//noinspection RedundantDefaultArgument
class MockitoSugarTest_212 extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar {
class MockitoSugarTest_212 extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar with TableDrivenPropertyChecks {

class Foo {
def bar = "not mocked"
val scenarios = Table(
("testDouble", "foo", "baz"),
("mock", () => mock[Foo], () => mock[Baz]),
("spy", () => spy(new Foo), () => spy(new ConcreteBaz))
)

def iHaveByNameArgs(normal: String, byName: => String, byName2: => String): String = ???
forAll(scenarios) { (testDouble, foo, baz) =>
testDouble should {

def iHaveByNameAndFunction0Args(normal: String, f0: () => String, byName: => String): String = ???
}

trait Baz {
def traitMethod(defaultArg: Int = 30, anotherDefault: String = "hola"): Int = ???
}

class SomeClass extends Foo with Baz
"work with default arguments in traits" in {
val testDouble = baz()

"mock[T]" should {
when(testDouble.traitMethodWithDefaultArgs(any, any)) thenReturn 69

"work with default arguments in traits" in {
val aMock = mock[Foo with Baz]
testDouble.traitMethodWithDefaultArgs() shouldBe 69

when(aMock.bar) thenReturn "mocked!"
when(aMock.traitMethod(any, any)) thenReturn 69
verify(testDouble).traitMethodWithDefaultArgs(30, "hola")
}

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

verify(aMock).traitMethod(30, "hola")
}

"work with by-name arguments and matchers (by-name arguments have to be the last ones when using matchers)" in {
val aMock = mock[Foo]
when(testDouble.iHaveByNameArgs(any, any, any)) thenReturn "mocked!"

when(aMock.iHaveByNameArgs(any, any, any)) thenReturn "mocked!"
testDouble.iHaveByNameArgs("arg1", "arg2", "arg3") shouldBe "mocked!"

aMock.iHaveByNameArgs("arg1", "arg2", "arg3") shouldBe "mocked!"

verify(aMock).iHaveByNameArgs(eqTo("arg1"), endsWith("g2"), eqTo("arg3"))
}
verify(testDouble).iHaveByNameArgs(eqTo("arg1"), endsWith("g2"), eqTo("arg3"))
}

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

when(aMock.iHaveByNameAndFunction0Args(any, any, any)) thenReturn "mocked!"
when(testDouble.iHaveByNameAndFunction0Args(any, any, any)) thenReturn "mocked!"

aMock.iHaveByNameAndFunction0Args("arg1", () => "arg2", "arg3") shouldBe "mocked!"
testDouble.iHaveByNameAndFunction0Args("arg1", () => "arg2", "arg3") shouldBe "mocked!"

verify(aMock).iHaveByNameAndFunction0Args(eqTo("arg1"), function0("arg2"), startsWith("arg"))
verify(testDouble).iHaveByNameAndFunction0Args(eqTo("arg1"), function0("arg2"), startsWith("arg"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package user.org.mockito

import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito}
import org.scalatest
import org.scalatest.WordSpec
import user.org.mockito.matchers.{ValueCaseClass, ValueClass}

class IdiomaticMockitoTest_213 extends WordSpec with scalatest.Matchers with IdiomaticMockito with ArgumentMatchersSugar {

class Foo {
def valueClass(n: Int, v: ValueClass): String = ???

def valueCaseClass(n: Int, v: ValueCaseClass): String = ???
}

"value class matchers" should {

"eqTo macro works with new syntax" in {
val aMock = mock[Foo]

aMock.valueClass(1, eqTo(new ValueClass("meh"))) shouldReturn "mocked!"
aMock.valueClass(1, new ValueClass("meh")) shouldBe "mocked!"
aMock.valueClass(1, eqTo(new ValueClass("meh"))) was called

aMock.valueClass(*, new ValueClass("moo")) shouldReturn "mocked!"
aMock.valueClass(11, new ValueClass("moo")) shouldBe "mocked!"
aMock.valueClass(*, new ValueClass("moo")) was called

val valueClass = new ValueClass("blah")
aMock.valueClass(1, eqTo(valueClass)) shouldReturn "mocked!"
aMock.valueClass(1, valueClass) shouldBe "mocked!"
aMock.valueClass(1, eqTo(valueClass)) was called

aMock.valueCaseClass(2, eqTo(ValueCaseClass(100))) shouldReturn "mocked!"
aMock.valueCaseClass(2, ValueCaseClass(100)) shouldBe "mocked!"
aMock.valueCaseClass(2, eqTo(ValueCaseClass(100))) was called

val caseClassValue = ValueCaseClass(100)
aMock.valueCaseClass(3, eqTo(caseClassValue)) shouldReturn "mocked!"
aMock.valueCaseClass(3, caseClassValue) shouldBe "mocked!"
aMock.valueCaseClass(3, eqTo(caseClassValue)) was called

aMock.valueCaseClass(*, ValueCaseClass(200)) shouldReturn "mocked!"
aMock.valueCaseClass(4, ValueCaseClass(200)) shouldBe "mocked!"
aMock.valueCaseClass(*, ValueCaseClass(200)) was called
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package user.org.mockito

import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
import org.scalatest.prop.TableDrivenPropertyChecks
import org.scalatest.{ Matchers, WordSpec }

//noinspection RedundantDefaultArgument
class MockitoSugarTest_213 extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar with TableDrivenPropertyChecks {

val scenarios = Table(
("testDouble", "foo", "baz"),
("mock", () => mock[Foo], () => mock[Baz]),
("spy", () => spy(new Foo), () => spy(new ConcreteBaz))
)

forAll(scenarios) { (testDouble, foo, baz) =>
testDouble should {

"work with default arguments in traits" in {
val testDouble = baz()

when(testDouble.traitMethodWithDefaultArgs(any, any)) thenReturn 69

testDouble.traitMethodWithDefaultArgs() shouldBe 69

verify(testDouble).traitMethodWithDefaultArgs(30, "hola")
}

"work with by-name arguments and matchers (by-name arguments have to be the last ones when using matchers)" in {
val testDouble = foo()

when(testDouble.iHaveByNameArgs(any, any, any)) thenReturn "mocked!"

testDouble.iHaveByNameArgs("arg1", "arg2", "arg3") shouldBe "mocked!"

verify(testDouble).iHaveByNameArgs(eqTo("arg1"), endsWith("g2"), eqTo("arg3"))
}

"work with by-name and Function0 arguments (by-name arguments have to be the last ones when using matchers)" in {
val testDouble = foo()

when(testDouble.iHaveByNameAndFunction0Args(any, any, any)) thenReturn "mocked!"

testDouble.iHaveByNameAndFunction0Args("arg1", () => "arg2", "arg3") shouldBe "mocked!"

verify(testDouble).iHaveByNameAndFunction0Args(eqTo("arg1"), function0("arg2"), startsWith("arg"))
}
}
}
}
10 changes: 10 additions & 0 deletions core/src/test/scala/user/org/mockito/IdiomaticMockitoTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,16 @@ class IdiomaticMockitoTest extends WordSpec with Matchers with IdiomaticMockito
}
}

"spy" should {
"interact correctly with the real object" in {
val it = spy(Iterator.continually("hello"))
val result = it.map(_.length)
it.next() wasNever called
result.next() shouldBe 5
it.next() wasCalled once
}
}

"doStub" should {
"stub a spy that would fail if the real impl is called" in {
val aSpy = spy(new Org)
Expand Down
7 changes: 6 additions & 1 deletion core/src/test/scala/user/org/mockito/TestModel.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package user.org.mockito
import user.org.mockito.matchers.{ ValueCaseClass, ValueClass }
import user.org.mockito.matchers.{ValueCaseClass, ValueClass}

class Foo {
def bar = "not mocked"

def iHaveByNameArgs(normal: String, byName: => String, byName2: => String): String = "not mocked"

def iHaveSomeDefaultArguments(noDefault: String, default: String = "default value"): String = "not mocked"

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

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

def iHaveByNameAndFunction0Args(normal: String, f0: () => String, byName: => String): String = "not mocked"

def returnBar: Bar = new Bar

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

trait Baz {
def traitMethod(arg: Int): ValueCaseClass = ValueCaseClass(arg)
def traitMethodWithDefaultArgs(defaultArg: Int = 30, anotherDefault: String = "hola"): Int = -1
}

class ConcreteBaz extends Baz
Expand Down