Skip to content

Commit b750963

Browse files
committed
Split ScalaDefaultAnswer into multiple, composable DefaultAnswers
1 parent 004d04d commit b750963

9 files changed

+142
-116
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,46 @@
11
package org.mockito
22

3-
import java.lang.reflect.Modifier.{ isAbstract, isFinal }
3+
import java.lang.reflect.Modifier.{isAbstract, isFinal}
44

55
import org.mockito.exceptions.base.MockitoException
66
import org.mockito.exceptions.verification.SmartNullPointerException
7-
import org.mockito.internal.stubbing.defaultanswers.ReturnsMoreEmptyValues
87
import org.mockito.internal.util.ObjectMethodsGuru.isToStringMethod
98
import org.mockito.invocation.InvocationOnMock
109
import org.mockito.stubbing.Answer
10+
import org.mockito.Answers._
1111

12-
import scala.util.{ Failure, Try }
12+
import scala.concurrent.Future
13+
import scala.util.{Failure, Try}
1314

14-
object ScalaDefaultAnswer extends Answer[Any] {
15+
trait DefaultAnswer extends Answer[Any] with Function[InvocationOnMock, Option[Any]] { self =>
16+
override def answer(invocation: InvocationOnMock): Any = apply(invocation).orNull
1517

16-
private val delegate = new ReturnsMoreEmptyValues
18+
def orElse(next: DefaultAnswer): DefaultAnswer = new DefaultAnswer {
19+
override def apply(invocation: InvocationOnMock): Option[Any] = self(invocation).orElse(next.apply(invocation))
20+
}
21+
}
22+
23+
object DefaultAnswer {
24+
implicit val defaultAnswer: DefaultAnswer = DefaultParametersHandler orElse ReturnsDefaults orElse SmartNulls
25+
}
1726

18-
override def answer(invocation: InvocationOnMock): Any =
27+
object DefaultParametersHandler extends DefaultAnswer {
28+
override def apply(invocation: InvocationOnMock): Option[Any] =
1929
if (invocation.getMethod.getName.contains("$default$") && !isAbstract(invocation.getMethod.getModifiers))
20-
invocation.callRealMethod()
21-
else
22-
Option(delegate.answer(invocation))
23-
.orElse(emptyValues.get(invocation.getMethod.getReturnType))
24-
.orElse(smartNull(invocation))
25-
.orNull
30+
Some(invocation.callRealMethod())
31+
else None
32+
}
2633

27-
private def smartNull(invocation: InvocationOnMock): Option[Any] = {
34+
object ReturnsDefaults extends DefaultAnswer {
35+
override def apply(invocation: InvocationOnMock): Option[Any] = Option(RETURNS_DEFAULTS.answer(invocation))
36+
}
37+
38+
object CallsRealMethods extends DefaultAnswer {
39+
override def apply(invocation: InvocationOnMock): Option[Any] = Option(CALLS_REAL_METHODS.answer(invocation))
40+
}
41+
42+
object SmartNulls extends DefaultAnswer {
43+
override def apply(invocation: InvocationOnMock): Option[Any] = {
2844
val returnType = invocation.getMethod.getReturnType
2945

3046
if (!returnType.isPrimitive && !isFinal(returnType.getModifiers))
@@ -38,13 +54,15 @@ object ScalaDefaultAnswer extends Answer[Any] {
3854
override def answer(currentInvocation: InvocationOnMock): Any =
3955
if (isToStringMethod(currentInvocation.getMethod))
4056
s"""SmartNull returned by this un-stubbed method call on a mock:
41-
|${unStubbedInvocation.toString}""".stripMargin
57+
|${unStubbedInvocation.toString}""".stripMargin
4258
else
4359
throw new SmartNullPointerException(
4460
s"""You have a NullPointerException because this method call was *not* stubbed correctly:
45-
|[$unStubbedInvocation] on the Mock [${unStubbedInvocation.getMock}]""".stripMargin)
61+
|[$unStubbedInvocation] on the Mock [${unStubbedInvocation.getMock}]""".stripMargin)
4662
}
63+
}
4764

65+
object ReturnsEmptyValues extends DefaultAnswer {
4866
private[mockito] lazy val emptyValues: Map[Class[_], AnyRef] = Map(
4967
classOf[Option[_]] -> Option.empty,
5068
classOf[List[_]] -> List.empty,
@@ -57,9 +75,11 @@ object ScalaDefaultAnswer extends Answer[Any] {
5775
classOf[Stream[_]] -> Stream.empty,
5876
classOf[Vector[_]] -> Vector.empty,
5977
classOf[Try[_]] -> Failure(new MockitoException("Auto stub provided by mockito-scala")),
78+
classOf[Future[_]] -> Future.failed(new MockitoException("Auto stub provided by mockito-scala")),
6079
classOf[BigDecimal] -> BigDecimal(0),
6180
classOf[BigInt] -> BigInt(0),
6281
classOf[StringBuilder] -> StringBuilder.newBuilder
6382
)
6483

84+
override def apply(invocation: InvocationOnMock): Option[Any] = emptyValues.get(invocation.getMethod.getReturnType)
6585
}

core/src/main/scala/org/mockito/IdiomaticMockito.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
package org.mockito
22

3-
import org.mockito.stubbing.{ Answer, OngoingStubbing }
4-
import MockitoSugar.{ verify, _ }
3+
import org.mockito.stubbing.{Answer, OngoingStubbing}
4+
import org.mockito.MockitoSugar.{verify, _}
55

66
import scala.reflect.ClassTag
77
import scala.reflect.runtime.universe.TypeTag
88

99
trait IdiomaticMockito extends MockCreator {
1010

11-
override def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: Answer[_]): T =
11+
override def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: DefaultAnswer): T =
1212
MockitoSugar.mock[T](name)
1313

1414
override def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T = MockitoSugar.mock[T](mockSettings)
1515

1616
override def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T = MockitoSugar.mock[T](defaultAnswer)
1717

18-
override def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: Answer[_] = ScalaDefaultAnswer): T =
18+
override def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: DefaultAnswer): T =
1919
MockitoSugar.mock[T]
2020

2121
override def spy[T](realObj: T): T = MockitoSugar.spy(realObj)

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

+8-11
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,18 @@ package org.mockito
1313

1414
import org.mockito.internal.creation.MockSettingsImpl
1515
import org.mockito.invocation.InvocationOnMock
16-
import org.mockito.stubbing.{ Answer, OngoingStubbing, Stubber }
17-
import org.mockito.verification.{ VerificationMode, VerificationWithTimeout }
16+
import org.mockito.stubbing.{Answer, OngoingStubbing, Stubber}
17+
import org.mockito.verification.{VerificationMode, VerificationWithTimeout}
1818

1919
import scala.collection.JavaConverters._
2020
import scala.reflect.ClassTag
2121
import scala.reflect.runtime.universe.TypeTag
2222

2323
private[mockito] trait MockCreator {
24-
25-
implicit val defaultAnswer: Answer[_] = ScalaDefaultAnswer
26-
27-
def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: Answer[_] = ScalaDefaultAnswer): T
24+
def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: DefaultAnswer): T
2825
def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T
2926
def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T
30-
def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: Answer[_]): T
27+
def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: DefaultAnswer): T
3128

3229
def spy[T](realObj: T): T
3330
def spyLambda[T <: AnyRef: ClassTag](realObj: T): T
@@ -105,7 +102,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
105102
* <code>verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value")</code>
106103
* as the value for the second parameter would have been null...
107104
*/
108-
override def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: Answer[_] = ScalaDefaultAnswer): T =
105+
override def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: DefaultAnswer): T =
109106
mock(withSettings)
110107

111108
/**
@@ -123,7 +120,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
123120
* as the value for the second parameter would have been null...
124121
*/
125122
override def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T =
126-
mock(withSettings(defaultAnswer))
123+
mock(withSettings(defaultAnswer.lift))
127124

128125
/**
129126
* Delegates to <code>Mockito.mock(type: Class[T], mockSettings: MockSettings)</code>
@@ -170,7 +167,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
170167
* <code>verify(aMock).iHaveSomeDefaultArguments("I'm not gonna pass the second argument", "default value")</code>
171168
* as the value for the second parameter would have been null...
172169
*/
173-
override def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: Answer[_]): T =
170+
override def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: DefaultAnswer): T =
174171
mock(withSettings.name(name))
175172

176173
/**
@@ -187,7 +184,7 @@ private[mockito] trait MockitoEnhancer extends MockCreator {
187184
/**
188185
* Delegates to <code>Mockito.withSettings()</code>, it's only here to expose the full Mockito API
189186
*/
190-
def withSettings(implicit defaultAnswer: Answer[_]): MockSettings =
187+
def withSettings(implicit defaultAnswer: DefaultAnswer): MockSettings =
191188
Mockito.withSettings().defaultAnswer(defaultAnswer)
192189

193190
/**

core/src/main/scala/org/mockito/integrations/scalatest/ResetMocksAfterEachTest.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package org.mockito.integrations.scalatest
22

33
import java.util.concurrent.ConcurrentHashMap
44

5+
import org.mockito.{DefaultAnswer, MockCreator, MockitoSugar, MockSettings}
56
import org.mockito.stubbing.Answer
6-
import org.mockito.{ MockCreator, MockSettings, MockitoSugar }
7-
import org.scalatest.{ Outcome, TestSuite }
7+
import org.scalatest.{Outcome, TestSuite}
88

99
import scala.collection.JavaConverters._
1010
import scala.reflect.ClassTag
@@ -34,7 +34,7 @@ trait ResetMocksAfterEachTest extends TestSuite with MockCreator { self: MockCre
3434
mock
3535
}
3636

37-
abstract override def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: Answer[_]): T =
37+
abstract override def mock[T <: AnyRef: ClassTag: TypeTag](implicit defaultAnswer: DefaultAnswer): T =
3838
addMock(super.mock[T])
3939

4040
abstract override def mock[T <: AnyRef: ClassTag: TypeTag](defaultAnswer: Answer[_]): T =
@@ -43,6 +43,6 @@ trait ResetMocksAfterEachTest extends TestSuite with MockCreator { self: MockCre
4343
abstract override def mock[T <: AnyRef: ClassTag: TypeTag](mockSettings: MockSettings): T =
4444
addMock(super.mock[T](mockSettings))
4545

46-
abstract override def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: Answer[_]): T =
46+
abstract override def mock[T <: AnyRef: ClassTag: TypeTag](name: String)(implicit defaultAnswer: DefaultAnswer): T =
4747
addMock(super.mock[T](name))
4848
}

core/src/main/scala/org/mockito/package.scala

+5
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,9 @@ package object mockito {
113113
i.getArgument[P10](10)
114114
))
115115

116+
implicit class AnswerOps[T](val a: Answer[T]) extends AnyVal {
117+
def lift: DefaultAnswer = new DefaultAnswer {
118+
override def apply(invocation: InvocationOnMock): Option[Any] = Option(a.answer(invocation))
119+
}
120+
}
116121
}

core/src/test/scala/org/mockito/MockitoScalaSessionTest.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class MockitoScalaSessionTest extends WordSpec with IdiomaticMockito with scalat
6262
}
6363
}
6464

65-
thrown.getMessage should startWith("Unexpected invocations found")
65+
thrown.getMessage should startWith("A NullPointerException was thrown, check if maybe related to")
6666
}
6767

6868
"check SmartNull" in {

core/src/test/scala/org/mockito/MockitoSugar$Test.scala

+5-31
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,14 @@
11
package org.mockito
22

3-
import org.scalatest.{ Matchers => ScalatestMatchers }
4-
import org.mockito.stubbing.Answer
5-
import org.scalatest.WordSpec
3+
import org.scalatest.{WordSpec, Matchers => ScalatestMatchers}
64

75
//noinspection RedundantDefaultArgument
86
class MockitoSugar$Test extends WordSpec with ScalatestMatchers {
97

108
class Foo {
119
def bar = "not mocked"
12-
13-
def iHaveSomeDefaultArguments(noDefault: String, default: String = "default value"): String =
14-
s"$noDefault - $default"
15-
16-
def iStartWithByNameArgs(byName: => String, normal: String): String = s"$normal - $byName"
17-
18-
def iHaveFunction0Args(normal: String, f0: () => String): String = s"$normal - $f0"
19-
}
20-
21-
class Bar {
22-
def iAlsoHaveSomeDefaultArguments(noDefault: String, default: String = "default value"): String =
23-
s"$noDefault - $default"
24-
}
25-
26-
trait Baz {
27-
def traitMethod(arg: Int): Int = arg + 12
2810
}
2911

30-
class SomeClass extends Foo with Baz
31-
3212
"mock[T]" should {
3313
"create a valid mock" in {
3414
val aMock = MockitoSugar.mock[Foo]
@@ -41,25 +21,19 @@ class MockitoSugar$Test extends WordSpec with ScalatestMatchers {
4121
"create a mock with default answer" in {
4222
val aMock = MockitoSugar.mock[Foo](Answers.CALLS_REAL_METHODS)
4323

44-
MockitoSugar
45-
.mockingDetails(aMock)
46-
.getMockCreationSettings
47-
.getDefaultAnswer should be theSameInstanceAs Answers.CALLS_REAL_METHODS
24+
aMock.bar shouldBe "not mocked"
4825
}
4926

5027
"create a mock with default answer from implicit scope" in {
51-
implicit val defaultAnswer: Answer[_] = Answers.CALLS_REAL_METHODS
28+
implicit val defaultAnswer: DefaultAnswer = CallsRealMethods
5229

5330
val aMock = MockitoSugar.mock[Foo]
5431

55-
MockitoSugar
56-
.mockingDetails(aMock)
57-
.getMockCreationSettings
58-
.getDefaultAnswer should be theSameInstanceAs Answers.CALLS_REAL_METHODS
32+
aMock.bar shouldBe "not mocked"
5933
}
6034

6135
"create a mock with name" in {
62-
implicit val defaultAnswer: Answer[_] = ScalaDefaultAnswer
36+
implicit val defaultAnswer: DefaultAnswer = ReturnsDefaults
6337

6438
val aMock = MockitoSugar.mock[Foo]("Nice Mock")
6539

core/src/test/scala/org/mockito/MockitoSugarTest.scala

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.mockito
22

33
import org.scalatest
4-
import org.mockito.stubbing.Answer
54
import org.scalatest.WordSpec
65

76
//noinspection RedundantDefaultArgument
@@ -56,15 +55,15 @@ class MockitoSugarTest
5655
"create a mock with default answer" in {
5756
val aMock = mock[Foo](Answers.CALLS_REAL_METHODS)
5857

59-
mockingDetails(aMock).getMockCreationSettings.getDefaultAnswer should be theSameInstanceAs Answers.CALLS_REAL_METHODS
58+
aMock.bar shouldBe "not mocked"
6059
}
6160

6261
"create a mock with default answer from implicit scope" in {
63-
implicit val defaultAnswer: Answer[_] = Answers.CALLS_REAL_METHODS
62+
implicit val defaultAnswer: DefaultAnswer = CallsRealMethods
6463

6564
val aMock = mock[Foo]
6665

67-
mockingDetails(aMock).getMockCreationSettings.getDefaultAnswer should be theSameInstanceAs Answers.CALLS_REAL_METHODS
66+
aMock.bar shouldBe "not mocked"
6867
}
6968

7069
"create a mock with name" in {
@@ -103,7 +102,7 @@ class MockitoSugarTest
103102
when(aMock.iStartWithByNameArgs("arg1", "arg2")) thenReturn "mocked!"
104103

105104
aMock.iStartWithByNameArgs("arg1", "arg2") shouldBe "mocked!"
106-
aMock.iStartWithByNameArgs("arg1", "arg3") shouldBe ""
105+
aMock.iStartWithByNameArgs("arg1", "arg3") shouldBe null
107106

108107
verify(aMock).iStartWithByNameArgs("arg1", "arg2")
109108
verify(aMock).iStartWithByNameArgs("arg1", "arg3")
@@ -115,7 +114,7 @@ class MockitoSugarTest
115114
when(aMock.iHaveFunction0Args(eqTo("arg1"), function0("arg2"))) thenReturn "mocked!"
116115

117116
aMock.iHaveFunction0Args("arg1", () => "arg2") shouldBe "mocked!"
118-
aMock.iHaveFunction0Args("arg1", () => "arg3") shouldBe ""
117+
aMock.iHaveFunction0Args("arg1", () => "arg3") shouldBe null
119118

120119
verify(aMock).iHaveFunction0Args(eqTo("arg1"), function0("arg2"))
121120
verify(aMock).iHaveFunction0Args(eqTo("arg1"), function0("arg3"))
@@ -138,7 +137,7 @@ class MockitoSugarTest
138137

139138
reset(aMock)
140139

141-
aMock.bar shouldBe ""
140+
aMock.bar shouldBe null
142141
}
143142
}
144143

0 commit comments

Comments
 (0)