Skip to content

Refactor value matchers and postFix dependent syntax #56

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 2 commits into from
Nov 9, 2018
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
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,29 @@ The library has independent developers, release cycle and versioning from core m
* The usage of `org.mockito.Answer[T]` was removed from the API in favour of [Function Answers](#function-answers)
* If you were using something like `doAnswer(_ => <something>).when ...` to lazily compute a return value when the method is actually called you should now write it like `doAnswer(<something>).when ...`, no need of passing a function as that argument is by-name
* If you have chained return values like `when(myMock.foo) thenReturn "a" thenReturn "b" etc...` the syntax has changed a bit to `when(myMock.foo) thenReturn "a" andThen "b" etc...`
* Idiomatic syntax has some changes to allow support of mixing values and argument matchers [Mix-and-Match](#mix-and-match)
* Idiomatic syntax has some changes to remove postFix operations and also allow support for mixing values and argument matchers [Mix-and-Match](#mix-and-match)
```scala
aMock.bar shouldCallRealMethod => aMock.bar shouldCall realMethod

aMock wasCalled on bar => aMock.bar was called
aMock wasCalled onlyOn bar => aMock.bar wasCalled onlyHere
aMock was never called on bar => aMock.bar was never called
aMock was never called on bar => aMock.bar wasNever called
aMock wasCalled twiceOn bar => aMock.bar wasCalled twice
aMock wasCalled sixTimesOn bar => aMock.bar wasCalled sixTimes
aMock was never called => aMock.bar wasNever called
aMock was never called again => aMock.bar wasNever calledAgain

"mocked!" willBe returned by aMock bar => "mocked!" willBe returned by aMock.bar
"mocked!" willBe answered by aMock bar => "mocked!" willBe answered by aMock.bar
((i: Int) => i * 10) willBe answered by aMock bar * => ((i: Int) => i * 10) willBe answered by aMock.bar(*)
theRealMethod willBe called by aMock bar => theRealMethod willBe called by aMock.bar
new IllegalArgumentException willBe thrown by aMock bar => new IllegalArgumentException willBe thrown by aMock.bar
```
* eqToVal matcher syntax was improved to look more natural [Value Class Matchers](#value-class-matchers)
```scala
verify(myObj).myMethod(eqToVal[MyValueClass](456)) => verify(myObj).myMethod(eqToVal(MyValueClass(456)))
myObj.myMethod(eqToVal[MyValueClass](456)) was called => myObj.myMethod(eqToVal(MyValueClass(456))) was called
```

## Getting started

Expand Down Expand Up @@ -94,7 +103,7 @@ when(myObj.myMethod(anyVal[MyValueClass]) thenReturn "something"

myObj.myMethod(MyValueClass(456)) shouldBe "something"

verify(myObj).myMethod(eqToVal[MyValueClass](456))
verify(myObj).myMethod(eqToVal(MyValueClass(456)))
```

## Improved ArgumentCaptor
Expand Down Expand Up @@ -240,7 +249,7 @@ val aMock = mock[Foo]

when(aMock.bar) thenReturn "mocked!" <=> aMock.bar shouldReturn "mocked!"
when(aMock.bar) thenReturn "mocked!" thenReturn "mocked again!" <=> aMock.bar shouldReturn "mocked!" andThen "mocked again!"
when(aMock.bar) thenCallRealMethod() <=> aMock.bar shouldCallRealMethod
when(aMock.bar) thenCallRealMethod() <=> aMock.bar shouldCall realMethod
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there any other things you can type after shouldCall?
If not, then I'm not sure it's worth splitting it, just my 2c

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was also postfix notation and had a similar compiler error if it had a follow up line

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about shouldReturn whateverRealMethdoReturns (not really sure what to put as the shouldReturn parameter, but it would make it more consistent if we can find something that reads nicely

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think shouldReturn whateverRealMethdoReturns looks too long, so shouldCall realMethod is probably better

another way looking at it can be aMock.bar should not be mocked

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not be would clash with ScalaTest, so it's a no go
I wasn't being literal with whateverRealMethdoReturns :P just looking for suggestions

when(aMock.bar).thenThrow[IllegalArgumentException] <=> aMock.bar.shouldThrow[IllegalArgumentException]
when(aMock.bar) thenThrow new IllegalArgumentException <=> aMock.bar shouldThrow new IllegalArgumentException
when(aMock.bar) thenAnswer(_ => "mocked!") <=> aMock.bar shouldAnswer "mocked!"
Expand All @@ -252,14 +261,14 @@ doAnswer(_.getArgument[Int](0) * 10).when(aMock).bar(any) <=> ((i: Int) =>
doCallRealMethod.when(aMock).bar <=> theRealMethod willBe called by aMock.bar
doThrow(new IllegalArgumentException).when(aMock).bar <=> new IllegalArgumentException willBe thrown by aMock.bar

verifyZeroInteractions(aMock) <=> aMock was never called
verifyZeroInteractions(aMock) <=> aMock wasNever called
verify(aMock).bar <=> aMock.bar was called
verify(aMock).bar(any) <=> aMock.bar(*) was called
verify(aMock, only).bar <=> aMock.bar wasCalled onlyHere
verify(aMock, never).bar <=> aMock.bar was never called
verify(aMock, never).bar <=> aMock.bar wasNever called
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively aMock.bar wasCalled zeroTimes or aMock.bar wasCalled never

it's more consistent with the rest of the api, though I admit wasNever Called (or was neverCalled) reads better

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I'll take a second look

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about wasNot called and wasNot calledAgain
I know still doesn't uses the wasCalled but reads much better and it allows you to copy/paste and just add 'Not' (I've found myself doing that many times, so I think it's a popular use case`

also using Not instead of Never seems nicer, the reason I didn't do it before was that not was a keyword and it clashed with the same defined by ScalaTest, but if I use it as part of the method name then that's not an issue anymore

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming things is hard, and there always will be who thinks it can have a better name, so i suggest juts pick something that compiles in all circumstances and be happy :)

i personally think that consistency is more important than following English, easy to remember => quick to use => more productive
but again, this is just me

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, but one of the main purposes of the DSL is to get rid of awkward reading sentences as well, and the current form is consistent with what it was there before (in both version 1.0.0 and 0.x.x) so I'm gonna keep it like this, history will judge me XD

Thanks a lot for your help!

verify(aMock, times(2)).bar <=> aMock.bar wasCalled twice
verify(aMock, times(6)).bar <=> aMock.bar wasCalled sixTimes
verifyNoMoreInteractions(aMock) <=> aMock was never called again
verifyNoMoreInteractions(aMock) <=> aMock wasNever calledAgain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively this can be
aMock wasCalled neverAgain
it looks more consistent with wasCalled twice and so on

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point


val order = inOrder(mock1, mock2) <=> InOrder(mock1, mock2) { implicit order =>
order.verify(mock2).someMethod() <=> mock2.someMethod() was called
Expand Down
21 changes: 10 additions & 11 deletions core/src/main/scala/org/mockito/IdiomaticMockito.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.mockito

import org.mockito.stubbing.{ DefaultAnswer, ScalaOngoingStubbing }
import org.mockito.MockitoSugar._
import org.mockito.VerifyMacro.{ AtLeast, AtMost, OnlyOn, Times }
import org.mockito.VerifyMacro._
import org.mockito.WhenMacro._

import scala.language.experimental.macros
Expand Down Expand Up @@ -30,15 +30,17 @@ trait IdiomaticMockito extends MockCreator {

def shouldReturn: ReturnActions[T] = macro WhenMacro.shouldReturn[T]

def shouldCallRealMethod: ScalaOngoingStubbing[T] = macro WhenMacro.shouldCallRealMethod[T]
def shouldCall(crm: RealMethod): ScalaOngoingStubbing[T] = macro WhenMacro.shouldCallRealMethod[T]

def shouldThrow: ThrowActions[T] = macro WhenMacro.shouldThrow[T]

def shouldAnswer: AnswerActions[T] = macro WhenMacro.shouldAnswer[T]

def was(called: Called.type)(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasMacro[T]

def was(n: Never): NeverInstance[T] = new NeverInstance(stubbing)
def wasNever(called: Called.type)(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasNotMacro[T]

def wasNever(called: CalledAgain)(implicit $ev: T <:< AnyRef): Unit = verifyNoMoreInteractions(stubbing.asInstanceOf[AnyRef])

def wasCalled(t: Times)(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasMacroTimes[T]

Expand Down Expand Up @@ -86,17 +88,14 @@ trait IdiomaticMockito extends MockCreator {

class On
class Never
//noinspection UnitMethodIsParameterless
class NeverInstance[T](mock: => T) {
def called(implicit order: VerifyOrder): Unit = macro VerifyMacro.wasNotMacro[T]
def called(again: Again)(implicit $ev: T <:< AnyRef): Unit = verifyNoMoreInteractions(mock.asInstanceOf[AnyRef])
}
class Again
class CalledAgain

val calledAgain = new CalledAgain

val realMethod = new RealMethod

val on = new On
val onlyHere = new OnlyOn
val never = new Never
val again = new Again
val once = Times(1)
val twice = Times(2)
val thrice = Times(3)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package org.mockito.matchers

import scala.language.experimental.macros

trait MacroBasedMatchers {

/**
* Wraps the standard 'ArgumentMatchers.eq()' matcher on the value class provided, this one requires the type to be explicit
*/
def eqToVal[T](value: Any)(implicit valueClassMatchers: ValueClassMatchers[T]): T = valueClassMatchers.eqToVal(value)
* To be used instead of eqTo when the argument is a value class
*/
def eqToVal[T](value: T): T = macro ValueClassMatchers.eqToValMatcher[T]

/**
* Wraps the standard 'any' matcher on the value class provided, this one requires the type to be explicit
* To be used instead of any when the argument is a value class
*/
def anyVal[T](implicit valueClassMatchers: ValueClassMatchers[T]): T = valueClassMatchers.anyVal
def anyVal[T]: T = macro ValueClassMatchers.anyValMatcher[T]

}
10 changes: 4 additions & 6 deletions core/src/test/scala/user/org/mockito/DoSomethingTest.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package user.org.mockito

import org.mockito.{ArgumentMatchersSugar, MockitoSugar}
import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
import org.mockito.invocation.InvocationOnMock
import org.scalatest.{WordSpec, Matchers => ScalaTestMatchers}

import scala.language.postfixOps
import org.scalatest.{ WordSpec, Matchers => ScalaTestMatchers }

class DoSomethingTest extends WordSpec with MockitoSugar with ScalaTestMatchers with ArgumentMatchersSugar {

Expand Down Expand Up @@ -54,15 +52,15 @@ class DoSomethingTest extends WordSpec with MockitoSugar with ScalaTestMatchers
"simplify answer API" in {
val aMock = mock[Foo]

doAnswer((i: Int, s: String) => i * 10 + s.toInt toString).when(aMock).doSomethingWithThisIntAndString(*, *)
doAnswer((i: Int, s: String) => (i * 10 + s.toInt).toString).when(aMock).doSomethingWithThisIntAndString(*, *)

aMock.doSomethingWithThisIntAndString(4, "2") shouldBe "42"
}

"simplify answer API (invocation usage)" in {
val aMock = mock[Foo]

doAnswer((i: InvocationOnMock) => i.getArgument[Int](0) * 10 + i.getArgument[String](1).toInt toString)
doAnswer((i: InvocationOnMock) => (i.getArgument[Int](0) * 10 + i.getArgument[String](1).toInt).toString)
.when(aMock)
.doSomethingWithThisIntAndString(*, *)

Expand Down
77 changes: 56 additions & 21 deletions core/src/test/scala/user/org/mockito/IdiomaticMockitoTest.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package user.org.mockito

import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito, VerifyOrder}
import org.mockito.captor.ArgCaptor
import org.mockito.exceptions.verification._
import org.mockito.invocation.InvocationOnMock
import org.mockito.{ ArgumentMatchersSugar, IdiomaticMockito }
import org.scalatest
import org.scalatest.WordSpec

import scala.language.postfixOps
import user.org.mockito.matchers.{ ValueCaseClass, ValueClass }

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

Expand All @@ -27,11 +26,15 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat

def highOrderFunction(f: Int => String): String = "not mocked"

def iReturnAFunction(v: Int): Int => String = i => i * v toString
def iReturnAFunction(v: Int): Int => String = i => (i * v).toString

def iBlowUp(v: Int, v2: String): String = throw new IllegalArgumentException("I was called!")

def iHaveTypeParamsAndImplicits[A, B](a: A, b: B)(implicit v3: Implicit[A]): String = "not mocked"

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

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

class Bar {
Expand All @@ -58,7 +61,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
}

"create a mock where I can mix matchers, normal and implicit parameters" in {
val aMock = mock[Foo]
val aMock = mock[Foo]
implicit val implicitValue: Implicit[Int] = mock[Implicit[Int]]

aMock.iHaveTypeParamsAndImplicits[Int, String](*, "test") shouldReturn "mocked!"
Expand All @@ -73,7 +76,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
"stub a real call" in {
val aMock = mock[Foo]

aMock.bar shouldCallRealMethod
aMock.bar shouldCall realMethod

aMock.bar shouldBe "not mocked"
}
Expand Down Expand Up @@ -117,10 +120,10 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
val aMock = mock[Foo]

aMock.doSomethingWithThisInt(*) shouldAnswer ((i: Int) => i * 10 + 2)
aMock.doSomethingWithThisIntAndString(*, *) shouldAnswer ((i: Int, s: String) => i * 10 + s.toInt toString)
aMock.doSomethingWithThisIntAndString(*, *) shouldAnswer ((i: Int, s: String) => (i * 10 + s.toInt).toString)
aMock.doSomethingWithThisIntAndStringAndBoolean(*, *, *) shouldAnswer ((i: Int,
s: String,
boolean: Boolean) => (i * 10 + s.toInt toString) + boolean)
boolean: Boolean) => (i * 10 + s.toInt).toString + boolean)

aMock.doSomethingWithThisInt(4) shouldBe 42
aMock.doSomethingWithThisIntAndString(4, "2") shouldBe "42"
Expand Down Expand Up @@ -167,7 +170,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
"allow using less params than method on answer stubbing" in {
val aMock = mock[Foo]

aMock.doSomethingWithThisIntAndStringAndBoolean(*, *, *) shouldAnswer ((i: Int, s: String) => i * 10 + s.toInt toString)
aMock.doSomethingWithThisIntAndStringAndBoolean(*, *, *) shouldAnswer ((i: Int, s: String) => (i * 10 + s.toInt).toString)

aMock.doSomethingWithThisIntAndStringAndBoolean(4, "2", v3 = true) shouldBe "42"
}
Expand All @@ -192,7 +195,7 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
"stub a method that returns a function" in {
val aMock = mock[Foo]

aMock.iReturnAFunction(*) shouldReturn (_.toString) andThen (i => (i * 2) toString) andThenCallRealMethod ()
aMock.iReturnAFunction(*) shouldReturn (_.toString) andThen (i => (i * 2).toString) andThenCallRealMethod ()

aMock.iReturnAFunction(0)(42) shouldBe "42"
aMock.iReturnAFunction(0)(42) shouldBe "84"
Expand Down Expand Up @@ -222,8 +225,8 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
val aSpy = spy(new Foo)

((i: Int) => i * 10 + 2) willBe answered by aSpy.doSomethingWithThisInt(*)
((i: Int, s: String) => i * 10 + s.toInt toString) willBe answered by aSpy.doSomethingWithThisIntAndString(*, *)
((i: Int, s: String, boolean: Boolean) => (i * 10 + s.toInt toString) + boolean) willBe answered by aSpy
((i: Int, s: String) => (i * 10 + s.toInt).toString) willBe answered by aSpy.doSomethingWithThisIntAndString(*, *)
((i: Int, s: String, boolean: Boolean) => (i * 10 + s.toInt).toString + boolean) willBe answered by aSpy
.doSomethingWithThisIntAndStringAndBoolean(*, *, v3 = true)
(() => "mocked!") willBe answered by aSpy.bar
"mocked!" willBe answered by aSpy.baz
Expand Down Expand Up @@ -265,12 +268,13 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
"check a mock was not used" in {
val aMock = mock[Foo]

aMock was never called
aMock wasNever called
aMock wasNever called

a[NoInteractionsWanted] should be thrownBy {
aMock.baz

aMock was never called
aMock wasNever called
}
}

Expand All @@ -279,12 +283,12 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
}

"check a mock was not used (with setup)" in new SetupNeverUsed {
aMock was never called
aMock wasNever called

a[NoInteractionsWanted] should be thrownBy {
aMock.baz

aMock was never called
aMock wasNever called
}
}

Expand Down Expand Up @@ -314,15 +318,15 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
}
}

"check a method was never called" in {
"check a method wasNever called" in {
val aMock = mock[Foo]

aMock.doSomethingWithThisIntAndString(*, "test") was never called
aMock.doSomethingWithThisIntAndString(*, "test") wasNever called

a[NeverWantedButInvoked] should be thrownBy {
aMock.doSomethingWithThisIntAndString(1, "test")

aMock.doSomethingWithThisIntAndString(*, "test") was never called
aMock.doSomethingWithThisIntAndString(*, "test") wasNever called
}
}

Expand Down Expand Up @@ -385,12 +389,12 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat

aMock.bar was called

aMock was never called again
aMock wasNever calledAgain

a[NoInteractionsWanted] should be thrownBy {
aMock.bar

aMock was never called again
aMock wasNever calledAgain
}
}

Expand Down Expand Up @@ -429,4 +433,35 @@ class IdiomaticMockitoTest extends WordSpec with scalatest.Matchers with Idiomat
}
}
}

"value class matchers" should {
"eqToVal works with new syntax" in {
val aMock = mock[Foo]

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

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

val value = ValueCaseClass(100)
aMock.valueCaseClass(3, eqToVal(value)) shouldReturn "mocked!"
aMock.valueCaseClass(3, ValueCaseClass(100)) shouldBe "mocked!"
aMock.valueCaseClass(3, eqToVal(value)) was called
}

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

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

aMock.valueCaseClass(2, anyVal[ValueCaseClass]) shouldReturn "mocked!"
aMock.valueCaseClass(2, ValueCaseClass(100)) shouldBe "mocked!"
aMock.valueCaseClass(2, anyVal[ValueCaseClass]) was called
}
}
}
10 changes: 4 additions & 6 deletions core/src/test/scala/user/org/mockito/MockitoSugarTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package user.org.mockito

import org.mockito.captor.ArgCaptor
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.{CallsRealMethods, DefaultAnswer}
import org.mockito.{ArgumentMatchersSugar, MockitoSugar}
import org.scalatest.{Matchers, WordSpec}

import scala.language.postfixOps
import org.mockito.stubbing.{ CallsRealMethods, DefaultAnswer }
import org.mockito.{ ArgumentMatchersSugar, MockitoSugar }
import org.scalatest.{ Matchers, WordSpec }

//noinspection RedundantDefaultArgument
class MockitoSugarTest extends WordSpec with MockitoSugar with Matchers with ArgumentMatchersSugar {
Expand Down Expand Up @@ -65,7 +63,7 @@ class MockitoSugarTest extends WordSpec with MockitoSugar with Matchers with Arg
"create a mock with nice answer API (multiple params)" in {
val aMock = mock[Foo]

when(aMock.doSomethingWithThisIntAndString(*,*)) thenAnswer ((i: Int, s: String) => i * 10 + s.toInt toString)
when(aMock.doSomethingWithThisIntAndString(*, *)) thenAnswer ((i: Int, s: String) => (i * 10 + s.toInt).toString)

aMock.doSomethingWithThisIntAndString(4, "2") shouldBe "42"
}
Expand Down
Loading