org.specs.mock.Mockito.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of specs_2.8.0.Beta1-RC8
Show all versions of specs_2.8.0.Beta1-RC8
specs is a Behaviour-Driven-Design
framework
The newest version!
/**
* Copyright (c) 2007-2009 Eric Torreborre
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software. Neither the name of specs nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package org.specs.mock
import org.specs.specification._
import org.specs.NumberOfTimes
import org.mockito.stubbing.Answer
import org.mockito.internal.stubbing.StubberImpl
import org.mockito.invocation.InvocationOnMock
import org.mockito.internal.verification.{ VerificationModeFactory, InOrderWrapper }
import org.mockito.internal.verification.api.{ VerificationInOrderMode, VerificationMode }
import org.mockito.internal.stubbing._
import org.mockito.stubbing.{ OngoingStubbing, Stubber }
import org.mockito.internal.progress._
import org.specs.matcher._
import org.specs.matcher.MatcherUtils._
import org.specs._
/**
* The Mockito trait can be mixed with Specifications to provide mocking capabilities using the Mockito library.
*
* It provides some syntactic sugar on top of the Mockito methods and is integrated with specs expectations:
*
* object spec extends Specification with Mockito {
*
* val m = mock[java.util.List[String]] // a concrete class would be mocked with: mock(new java.util.LinkedList[String])
*
* // stub a method call with a return value
* m.get(0) returns "one"
*
* // call the method
* m.get(0)
*
* // verify that the call happened, this is an expectation which will throw a FailureException if that is not the case
* m.get(0) was called
* m.get(1) wasnt called // we can also check that other calls did not occur
* }
*
*
* See the method descriptions for more usage examples.
*/
trait Mockito extends MockitoLifeCycle with CalledMatchers with InteractionMatchers with CalledInOrderMatchers with MockitoStubs with MockitoMatchers
/**
* This trait allows the initialization of mocks when defined with an annotation:
* @Mock val l: List[String] = null
*
* The beforeExpectations method is overriden instead of the beforeExample method. This doesn't make a big difference but it helps the compiler
* in the Eclipse plugin not to crash too often.
*/
trait MockitoLifeCycle extends LifeCycle {
/** variable used to avoid multiple initializations. */
private var initialized = false
/** The mocks are reinitialized before each tests. */
override def beforeExpectations(e: Examples) = {
super.beforeExpectations(e)
if (!initialized) {
initialized = true
org.mockito.MockitoAnnotations.initMocks(this)
}
}
}
/**
* This trait provides methods to declare expectations on mock calls:
*
* mockedList.get(0) was called
* mockedList.get(0) wasnt called
* mockedList.get(0) was notCalled
*
*
*
* where called is a Matcher which accepts supplementary methods to change the Mockito verification method:
*
* mockedList.get(0) was called.once // similar to was called
* mockedList.get(0) was called.twice
* mockedList.get(0) was called(3.times)
* mockedList.get(0) was called.atLeastOnce // or called.atLeast(once)
* mockedList.get(0) was called.atLeastTwice
* mockedList.get(0) was called.atLeast(3.times)
* mockedList.get(0) was called.atMostOnce
* mockedList.get(0) was called.atMostTwice
* mockedList.get(0) was called.atMost(3.times)
* mockedList.get(0) was called.exclusively // equivalent to called(0.times)
*
*
*/
trait CalledMatchers extends ExpectableFactory with NumberOfTimes with CalledInOrderMatchers {
/** delegate to Mockito. */
protected val mocker: org.mockito.MockitoMocker
/** create a CalledMock object for a method call. */
implicit def theMethod(c: =>Any) = new CalledMock(c)
/** provides methods creating calls expectations. */
class CalledMock(c: =>Any) {
def was(callMatcher: CalledMatcher) = {
theValue(c) must callMatcher
}
def wasnt(callMatcher: CalledMatcher) = {
theValue(c) must (callMatcher.times(0))
}
def on(m: AnyRef) = MockCall(Some(m), () => c)
}
/** @return a new CalledMatcher. */
def called = new CalledMatcher
/** @return a new CalledMatcher().times(range.n) */
def called(r: RangeInt): CalledMatcher = new CalledMatcher().times(r.n)
/** @return a CalledMatcher with times(0). */
def notCalled = new CalledMatcher().times(0)
/** @return a new CalledInOrder */
def calledInOrder: CalledInOrderMatcher = called.inOrder
/** this value can be used in mock.method was called.atLeast(once). */
val once = new RangeInt(1)
/** Matcher accepting a call and checking if the method was called according to the verification mode: once, times(2), atLeast(once,...) */
class CalledMatcher extends Matcher[Any] with HasVerificationMode {
def apply(v: =>Any) = {
mocker.verify(this.verificationMode)
var result = (true, "The method was called", "The method was not called")
try {
v
} catch {
case e => {
result = (false, "The method was called", "The method was not called as expected:" + e.getMessage)
}
}
result
}
/** create a CalledInOrderMatcher. Alias for calledInOrder. */
def inOrder = new CalledInOrderMatcher
}
}
/**
* This trait provides functions to set the verification mode for InOrder verifications.
* This means that it only supports AtLeast and Times verification modes.
*/
trait HasInOrderVerificationMode {
import Sugar._
protected var verificationMode = org.mockito.Mockito.times(1)
/** verification mode = times(1). This is the default. */
def once: this.type = this
/** verification mode = times(2). */
def twice: this.type = this.times(2)
/** verification mode = times(i). */
def times(i: Int): this.type = setVerificationMode(org.mockito.Mockito.times(i))
/** verification mode = atLeast(1). */
def atLeastOnce: this.type = this.atLeast(1)
/** verification mode = atLeast(2). */
def atLeastTwice: this.type = this.atLeast(2)
/** verification mode = atLeast(range.n). */
def atLeast(r: RangeInt): this.type = setVerificationMode(VerificationModeFactory.atLeast(r.n))
/** sets the verification mode. */
def setVerificationMode(v: VerificationMode): this.type = {
verificationMode = v
this
}
}
/**
* This trait provides functions to set the verification mode
*/
trait HasVerificationMode extends HasInOrderVerificationMode {
import Sugar._
/** verification mode = atMost(1). */
def atMostOnce: this.type = atMost(1)
/** verification mode = atMost(2). */
def atMostTwice: this.type = atMost(2)
/** verification mode = atMost(range.n). */
def atMost(r: RangeInt): this.type = {
verificationMode = VerificationModeFactory.atMost(r.n)
this
}
}
/**
* This trait provides an additional matcher to check if some methods methods have been called in the right order on mocks:
* theMethod(m1.get(0)).on(m1).atLeastOnce then
* theMethod(m2.get(0)).on(m2) were called.inOrder
*
* // the implicit definition can also be removed but additional parenthesis are needed
*
* (m1.get(0) on m1).atLeastOnce then
* (m2.get(0) on m2) were called.inOrder
*
*
*/
trait CalledInOrderMatchers extends ExpectableFactory with NumberOfTimes {
/** delegate to Mockito. */
protected val mocker: org.mockito.MockitoMocker
/** provides methods creating in order calls expectations. */
case class MockCall(mock: Option[AnyRef], result: () => Any) extends HasInOrderVerificationMode {
def this(result: () => Any) = this(None, result)
def verifInOrderMode = verificationMode.asInstanceOf[VerificationInOrderMode]
def then(other: MockCall): MockCallsList = MockCallsList(List(this)).then(other)
}
/** represent a sequence of in order calls. */
case class MockCallsList(calls: List[MockCall]) {
def then(call: MockCall): MockCallsList = MockCallsList(calls ::: List(call))
def were(matcher: CalledInOrderMatcher) = calls must matcher
}
/** Matcher accepting a list of call and checking if they happened in the specified order. */
class CalledInOrderMatcher extends Matcher[List[MockCall]] {
def apply(calls: =>List[MockCall]) = {
var result = (true, "The methods were called in the right order", "The methods were not called in the right order")
try {
val mocksToBeVerifiedInOrder = java.util.Arrays.asList(calls.flatMap(_.mock).toArray: _*)
calls.foreach { call =>
call.mock.map(mocker.verify(_, new InOrderWrapper(call.verifInOrderMode,
mocksToBeVerifiedInOrder)))
call.result()
}
} catch {
case e => result = (false, "The methods were called in the right order", "The methods were not called in the right order:" +
e.getMessage.replace("Verification in order failure", "").
replace("\n", " ").replace(" ", " "))
}
result
}
}
}
/**
* This trait provides an additional matcher to check if no more methods have been called on a mock:
* mock had noMoreCalls
*
*/
trait InteractionMatchers extends ExpectableFactory {
/** delegate to Mockito. */
protected val mocker: org.mockito.MockitoMocker
/** @return an object allowing the creation of expectations for mock interactions.*/
implicit def theMock[T <: AnyRef](m: =>T) = new MockObject(m)
/** object allowing the creation of expectations for mock interactions like: mock had noMoreCalls. */
class MockObject[T <: AnyRef](m: =>T) {
def had(interactionMatcher: InteractionMatcher[T]) = {
m must interactionMatcher
}
}
/** @return a NoMoreCallsMatcher. */
def noMoreCalls[T <: AnyRef] = new NoMoreCalls[T]
/** This class allows the expecations to potentially use other ways of checking the interactions on a mock.*/
abstract class InteractionMatcher[T <: AnyRef] extends Matcher[T]
/** This matcher checks if the mock had no more calls by calling Mockito.verifyNoMoreInteractions */
class NoMoreCalls[T <: AnyRef] extends InteractionMatcher[T] {
def apply(m: =>T) = {
var result = (true, "The mock wasn't called anymore", "The mock was called")
try {
mocker.verifyNoMoreInteractions(m)
} catch {
case e => {
result = (false, "The method wasn't called anymore", e.getMessage)
}
}
result
}
}
}
/**
* This trait provides functionalities to declare stub values on method calls.
*
* Usage:
*
* mockedList.get(0) returns "one"
* mockedList.get(0) returns ("one", "two")
* mockedList.get(0) throws new Exception("unexpected")
* mockedList.get(0) answers ( i => "value " + i.toString )
*
*
*
* It is also possible to chain stubs like this:
*
* mockedList.get(0) returns "one" thenReturns "two"
* mockedList.get(0) returns "one" thenThrows new Exception("unexpected now")
*
*/
trait MockitoStubs extends MocksCreation {
/** delegate to Mockito. */
protected val mocker: org.mockito.MockitoMocker
/** @return an object supporting the stub methods. */
implicit def theStubbed[T](c: =>T) = new Stubbed(c)
/**
* This class provide stub methods like returns, throws and answers.
* Internally it calls Mockito.when(mock call).thenReturn(returnValue)
*/
class Stubbed [T](c: =>T) {
def returns(t: T, t2: T*): OngoingStubbing[T] = {
if (t2.isEmpty)
mocker.when(c).thenReturn(t)
else { // written like this to avoid a 2.8 compiler error: "cannot find class manifest for element type of T*"
var stub = mocker.when(c).thenReturn(t)
t2 foreach { x =>
stub = stub.thenReturn(x)
}
stub
}
}
def answers(function: Any => T) = mocker.when(c).thenAnswer(new MockAnswer(function))
def throws[E <: Throwable](e: E*): OngoingStubbing[T] = {
if (e.isEmpty) throw new java.lang.IllegalArgumentException("The parameter passed to throws must not be empty")
var stub = mocker.when(c).thenThrow(e.head)
e.drop(1) foreach { x =>
stub = stub.thenThrow(x)
}
stub
}
}
/** @return an object allowing the chaining of returned values on doNothing calls. */
implicit def aStubber(stub: =>Stubber) = new AStubber(stub)
/** provide stub chain methods. */
class AStubber[T](stub: =>Stubber) {
def thenReturn[T](t: T) = stub.doReturn(t)
def thenThrow[E <: Throwable](e: E) = stub.doThrow(e)
}
/** @return an object allowing the chaining of stub values. */
implicit def anOngoingStubbing[T](stub: =>OngoingStubbing[T]) = new AnOngoingStubbing(stub)
/** provide stub chain methods. */
class AnOngoingStubbing[T](stub: =>OngoingStubbing[T]) {
def thenReturns(t: T) = stub.thenReturn(t)
def thenThrows[E <: Throwable](e: E) = stub.thenThrow(e)
}
/** allows to use a specs matcher to match parameters by encapsulating it as a Hamcrest matcher. */
implicit def argThat[T](m: org.specs.matcher.Matcher[T]): T = org.mockito.Matchers.argThat(new org.specs.mock.HamcrestMatcherAdapter(m))
/** allows to use a hamcrest matchers to match parameters. */
def argThat[T](m: org.hamcrest.Matcher[T]): T = org.mockito.Matchers.argThat(m)
}
trait MocksCreation {
/** delegate to Mockito static methods. */
protected val mocker = new org.mockito.MockitoMocker
/**
* create a mock object: val m = mock[java.util.List[Stringg]]
*/
def mock[T](implicit m: scala.reflect.Manifest[T]): T = mocker.mock(m)
/**
* create a mock object with smart return values: val m = smartMock[java.util.List[Stringg]]
*
* This is the equivalent of Mockito.mock(List.class, SMART_NULLVALUES) but testing shows that it is not working well with Scala.
*/
def smartMock[T](implicit m: scala.reflect.Manifest[T]): T = mocker.smartMock(m)
/**
* create a spy on an object.
*
* A spy is a real object but can still have some of its methods stubbed. However the syntax for stubbing a spy is a bit different than
* with a mock:
*
* val s = spy(new LinkedList[String])
* doReturn("one").when(s).get(0) // instead of s.get(0) returns "one" which would throw an exception
*
*
*/
def spy[T](m: T): T = mocker.spy(m)
/** delegate to MockitoMocker doReturn. */
def doReturn[T](t: T) = mocker.doReturn(t)
/** delegate to MockitoMocker doAnswer with a MockAnswer object using the function f. */
def doAnswer[T](f: Any => T) = mocker.doAnswer(new MockAnswer(f))
/** delegate to MockitoMocker doAnswer. */
def doAnswer[T](a: Answer[T]) = mocker.doAnswer(a)
/** delegate to MockitoMocker doThrow. */
def doThrow[E <: Throwable](e: E) = mocker.doThrow(e)
/** delegate to MockitoMocker doNothing. */
def doNothing = mocker.doNothing
/**
* This class is an implementation of the Answer interface allowing to pass functions as an answer.
*
* It does a bit of work for the client:
*
* // if the method has one parameter and the function also, the parameter is passed
* mock.get(0) answers ( i => i.toString )
*
* // if the method has one parameter and the function has two, the mock is passed as the second argument
* mock.get(0) answers { (i, mock) => i.toString + " for mock " + mock.toString }
*
* Similarly a mocked method with no parameters can use a function with one parameter. In that case, the mock will be passed
* mock.size answers { mock => mock.hashCode }
*
* In any other cases, if f is a function of 1 parameter, the array of the method parameters will be passed and if the function has
* 2 parameters, the second one will be the mock.
*
*/
class MockAnswer[T](function: Any => T) extends Answer[T] {
def answer(invocation: InvocationOnMock): T = {
val args = invocation.getArguments
val mock = invocation.getMock
if (args.size == 0) {
function match {
case f: Function0[_] => f()
case f: Function1[_,_] => f(mock)
}
} else if (args.size == 1) {
function match {
case f: Function1[_, _] => f(args(0))
case f: Function2[_, _, _] => f(args(0), mock)
}
} else {
function match {
case f: Function1[_, _] => f(args)
case f: Function2[_, _, _] => f(args, mock)
}
}
}
}
}
trait MockitoMatchers {
def any[T](implicit m: scala.reflect.Manifest[T]): T = org.mockito.Matchers.isA(m.erasure).asInstanceOf[T]
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy