All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.specs.mock.Mockito.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2007-2010 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.util.Control._
import org.specs.specification._
import org.specs.NumberOfTimes
import org.mockito.InOrder
import org.mockito.stubbing.Answer
import org.mockito.internal.stubbing.StubberImpl
import org.mockito.invocation.InvocationOnMock
import org.mockito.internal.InOrderImpl 
import org.mockito.verification.{ VerificationMode }
import org.mockito.internal.stubbing._
import org.mockito.stubbing.{ OngoingStubbing, Stubber }
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:
 * 
 * class specification 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
 *   there was one(m).get(0)
 *   there was no(m).get(1) // 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 MockitoStubs with MockitoMatchers with MockitoFunctions 

/**
 * This trait provides methods to create mocks and spies.
 */
trait MocksCreation extends TheMockitoMocker {
  /**
   * create a mock object: val m = mock[java.util.List[Stringg]]
   */
  def mock[T](implicit m: scala.reflect.ClassManifest[T]): T = mocker.mock(m)
  /**
   * create a mock object with a name: val m = mockAs[java.util.List[String]]("name")
   */
  def mockAs[T](name: String)(implicit m: scala.reflect.ClassManifest[T]): T = mocker.mock(name)(m)
  /**
   * implicit allowing the following syntax for a named mock: val m = mock[java.util.List[String]],as("name")
   */
  implicit def mockToAs[T](t: =>T)(implicit m: scala.reflect.ClassManifest[T]) = new NamedMock(t)(m)
  
  /** support class to create a mock object with a name */
  class NamedMock[T](t: =>T)(implicit m: scala.reflect.ClassManifest[T]) {
    def as(name: String): T = mockAs[T](name)
  }

  /**
   * 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.ClassManifest[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)
}

/**
 * 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:
 * 
 * there was one(mockedList).get(0)
 * there was no(mockedList).get(0)
 * 
 * there was two(mockedList).get(0)
 * there was three(mockedList).get(0)
 * there was 4.times(mockedList).get(0)
 *
 * there was atLeastOne(mockedList).get(0)
 * there was atLeastTwo(mockedList).get(0)
 * there was atLeastThree(mockedList).get(0)
 * there was atLeast(4)(mockedList).get(0)
 * there was atMostOne(mockedList).get(0)
 * there was atMostTwo(mockedList).get(0)
 * there was atMostThree(mockedList).get(0)
 * there was atMost(4)(mockedList).get(0)
 * 
 * It is also possible to use a different wording:
 * 
 * there were two(mockedList).get(0)
 * got { two(mockedList).get(0) }
 * 
 * 
 */
trait CalledMatchers extends ExpectableFactory with NumberOfTimes with TheMockitoMocker {
  /** temporary InOrder object to accumulate mocks to verify in order */
  private var inOrder: Option[InOrderImpl] = None
  /** this matcher evaluates an expression containing mockito calls verification */
  private class CallsMatcher[T] extends Matcher[T] {
    def apply(v: =>T) = {
      var result = (true, "The mock was called as expected", "The mock was not called as expected")
      try { 
        v 
      } catch {
        case e => { 
          result = (false, "The mock was called as expected", "The mock was not called as expected: " + e.getMessage)
        }
      }
      result
    }
  }
  
  /** create an object supporting 'was' and 'were' methods */
  def there = new Calls
  /** 
   * class supporting 'was' and 'were' methods to forward mockito calls to the CallsMatcher matcher 
   */
  class Calls {
    def were[T](calls: =>T) = was(calls)
    def was[T](calls: =>T) = {
      calls must new CallsMatcher[T]
    }
  }
  /**
   * alias for 'there was'
   */
  def got[T](t: =>T) = there was t
  /**
   * implicit definition to be able to declare a number of calls 3.times(m).clear()
   */
  implicit def rangeIntToTimes(r: RangeInt) = new RangeIntToTimes(r)
  /**
   * class providing a apply method to be able to declare a number of calls:
   *   3.times(m).clear() is actually 3.times.apply(m).clear()
   */
  class RangeIntToTimes(r: RangeInt) {
    def apply[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.times(r.n))
  }
  /**
   * verify that a mock has been called appropriately
   * if an inOrder object has been previously created (which means we're verifying the mocks calls order),
   * then the mock is added to the inOrder object and the inOrder object is used for the verification.
   * 
   * Otherwise a normal verification is performed
   */
  private def verify[T <: AnyRef](mock: =>T, v: VerificationMode) = {
    inOrder map { ordered => 
      val mocksList = ordered.getMocksToBeVerifiedInOrder()
      if (!mocksList.contains(mock)) {
        mocksList.add(mock)
        inOrder = Some(new InOrderImpl(mocksList))
      }
    }
    mocker.verify(inOrder, mock, v)
  }
  /** no call made to the mock */
  def no[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.never())
  /** one call only made to the mock */
  def one[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.times(1))
  /** two calls only made to the mock */
  def two[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.times(2))
  /** three calls only made to the mock */
  def three[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.times(3))
  /** at least n calls made to the mock */
  def atLeast[T <: AnyRef](i: Int)(mock: =>T) = verify(mock, org.mockito.Mockito.atLeast(i))
  /** at least 1 call made to the mock */
  def atLeastOne[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.atLeast(1))
  /** at least 2 calls made to the mock */
  def atLeastTwo[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.atLeast(2))
  /** at least 3 calls made to the mock */
  def atLeastThree[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.atLeast(3))
  /** at most n calls made to the mock */
  def atMost[T <: AnyRef](i: Int)(mock: =>T) = verify(mock, org.mockito.Mockito.atMost(i))
  /** at most 1 call made to the mock */
  def atMostOne[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.atMost(1))
  /** at most 2 calls made to the mock */
  def atMostTwo[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.atMost(2))
  /** at most 3 calls made to the mock */
  def atMostThree[T <: AnyRef](mock: =>T) = verify(mock, org.mockito.Mockito.atMost(3))
  /** no more calls made to the mock */
  def noMoreCallsTo[T <: AnyRef](mock: =>T) = mocker.verifyNoMoreInteractions(mock)
  /** implicit def supporting calls in order */
  implicit def toInOrderMode[T](calls: =>T) = new ToInOrderMode(calls)
  /** 
   * class defining a then method to declare that calls must be made in a specific order.
   * 
   * The orderedBy method can be used to declare the mock order if there are several mocks
   */
  class ToInOrderMode[T](calls: =>T) {
    def then[U](otherCalls: =>U) = {
      val newOrder = inOrder match {
        case Some(o) => Some(o)
        case None => Some(new InOrderImpl(new java.util.ArrayList[Object]))
      }
      val f = () => setTemporarily(inOrder, newOrder, (o:Option[InOrderImpl]) => inOrder = o) {
        calls
        otherCalls 
      }
      f() must new CallsMatcher
    }
    /** specify the mocks which are to be checked in order */
    def orderedBy(mocks: AnyRef*) = {
      setTemporarily(inOrder, Some(new InOrderImpl(java.util.Arrays.asList(mocks.toArray: _*) )), (o:Option[InOrderImpl]) => inOrder = o) {
        calls
      } 
    }
  }
}
/**
 * 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 MockitoMocker doAnswer with a MockAnswer object using the function f. */
  def doAnswer[T](f: Any => T) = mocker.doAnswer(new MockAnswer(f))
  
  /** @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 ClassManifest 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)

    /** 
   * 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[_] => return f()
           case f: Function1[_,_] => return f(mock)
         }
       } else if (args.size == 1) {
         function match {
           case f: Function1[_, _] => return f(args(0))
         }
         function match {
           case f2: Function2[_, _, _] => return f2(args(0), mock)
         }
       } else {
         function match {
           case f: Function1[_, _] => return f(args)
         }
         function match {
           case f2: Function2[_, _, _] => return f2(args, mock)
         }
       }
     } 
  }
}
/**
 * shortcuts to standard Mockito functions
 */
trait MockitoFunctions extends TheMockitoMocker {
    /** delegate to MockitoMocker doReturn. */
  def doReturn[T](t: T) = mocker.doReturn(t)
  /** 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
}
/**
 * Type-inference friendly Mockito matcher for 'any'
 */
trait MockitoMatchers {
  def any[T](implicit m: scala.reflect.ClassManifest[T]): T = org.mockito.Matchers.isA(m.erasure).asInstanceOf[T]
}
/** delegate to Mockito static methods with appropriate type inference. */
trait TheMockitoMocker {
  private[specs] val mocker = new org.mockito.MockitoMocker
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy