Skip to content

Commit

Permalink
Stubs now discriminate given arguments.
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaeldff committed Oct 7, 2012
1 parent ee1f0da commit e2af43e
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ object ProxyFactory {

type Subject = AnyRef
type Arguments = Array[AnyRef]
type Method = ReflectMethod
type Method = String
case class Invocation(subject:Subject, method:Method, arguments:Arguments)

type ProxyReaction = (Subject, Method, Arguments) => AnyRef
type ProxyReaction = Invocation => AnyRef

def apply[T:ClassTag](reaction: ProxyReaction):T = {
val javaClass = implicitly[ClassTag[T]].runtimeClass
Expand All @@ -19,8 +20,8 @@ object ProxyFactory {

def make[Result](cls:Class[_]*)(reaction:ProxyReaction):Result = {
val handler = new InvocationHandler {
def invoke(obj:Object, method:Method, args:Array[Object]) =
reaction(obj, method, args)
def invoke(obj:Object, method:ReflectMethod, args:Array[Object]) =
reaction(Invocation(obj, method.getName, (if (args == null) Array[Object]() else args)))
}

Proxy.newProxyInstance(Thread.currentThread.getContextClassLoader, Array(cls:_*), handler).asInstanceOf[Result]
Expand Down
23 changes: 18 additions & 5 deletions src/main/scala/net/rafaelferreira/goose/stubs/StubDouble.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,38 @@ package stubs

import scala.language.experimental.macros
import scala.reflect.ClassTag
import org.specs2.matcher.Matcher

import ProxyFactory._


case class StubDouble[T: ClassTag](expectations: Seq[Expectation[T]] = Vector()) extends InitializedDouble[T] {
import ProxyFactory._

lazy val results =
expectations.foldLeft(Map[String, AnyRef]()) {(map, expectation) =>
map + (expectation.methodCalled -> expectation.result)
}

def expecting[R](expectation:Expectation[T]):StubDouble[T] =
copy(expectations = expectations :+ expectation)
copy(expectations = expectation +: expectations)

def value: T =
ProxyFactory { (obj:Subject, method:Method, args: Arguments) =>
results(method.getName)
ProxyFactory { invocation =>
expectations.find(_ appliesTo invocation).map(_.result).getOrElse( default )
}

def default = null
}

case class Expectation[T:ClassTag](call: Call[T], result: AnyRef) {
val methodCalled = call.method

def appliesTo(invocation: Invocation): Boolean = {
def methodMatches = call.method == invocation.method
def aritiesMatch = call.args.size == invocation.arguments.size
def argumentsMatch = call.args.zip(invocation.arguments).forall {
case (expected:Matcher[Any], actual) => expected.test(actual)
case (literalExpected, actual) => literalExpected == actual
}
methodMatches && aritiesMatch && argumentsMatch
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ trait StubsDSL { this: GooseSpecificationDSL =>
def apply(testDouble: TestDouble[T]): TestDouble[T] =
testDouble match {
case UninitializedDouble => StubDouble(Vector(Expectation(call,result)))
case StubDouble(expectations) => StubDouble(expectations :+ Expectation(call, result))
case double:StubDouble[T] => double expecting Expectation(call, result)
case _ => sys.error("Cannot mix stub with non-stub expectations.")
}
}
Expand Down
52 changes: 47 additions & 5 deletions src/test/scala/net/rafaelferreira/goose/stubs/StubsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ class StubsSpec extends Specification {
}

def is = "Stub" ^ p ^
"stub a no-args method" ! stubNoArgs ^
"stub a method that takes an argument typed to a trait" ! stubWithArgs ^
"stub a method that may take different arguments" ! stubWithDifferentArgs
"Giving argument matchers" ^
"stub a no-args method" ! stubNoArgs ^
"stub a method that takes an argument typed to a trait" ! stubWithArgs ^
"stub a method that may take different arguments" ! stubWithDifferentArgs ^
"stub a method twice, with the second expectation overriding the first " ^ stubOverriding ^ p ^
"Giving literal arguments" ^
"stub a method that may take different arguments" ! stubWithDifferentLiteralArgs ^ p ^
"Giving mixed arguments" ^
"stub a method that may take different arguments" ! stubWithDifferentMixedArgs


def stubNoArgs = {
val stub = new StubDouble[Foo]
Expand All @@ -31,8 +38,43 @@ class StubsSpec extends Specification {
def stubWithDifferentArgs = {
val stub = new StubDouble[Foo]
val barParameter = new Bar {}
val expectingFirst = stub.expecting(Expectation(Call(this, "takesBar", Seq(===(barParameter))), "result"))
expectingFirst.value.takesBar(barParameter) must_== "result"
val differentBarParameter = new Bar {}
val expectingFirst = stub.expecting(Expectation(Call(this, "takesBar", Seq(===(barParameter))), "first"))
val expectingBoth = expectingFirst.expecting(Expectation(Call(this, "takesBar", Seq(===(differentBarParameter))), "second"))
(expectingBoth.value.takesBar(barParameter) must_== "first") and (expectingBoth.value.takesBar(differentBarParameter) must_== "second")
}

def stubOverriding = {
val stub = new StubDouble[Foo]
val barParameter = new Bar {}
val expectingFirst = stub.expecting(Expectation(Call(this, "takesBar", Seq(===(barParameter))), "first"))
val expectingSecond = expectingFirst.expecting(Expectation(Call(this, "takesBar", Seq(===(barParameter))), "second"))
expectingSecond.value.takesBar(barParameter) must_== "second"
}

def stubWithDifferentLiteralArgs = {
val stub = new StubDouble[Foo]
val barParameter = new Bar {}
val differentBarParameter = new Bar {}
val expectingFirst = stub.expecting(Expectation(Call(this, "takesBar", Seq(barParameter)), "first"))
val expectingBoth = expectingFirst.expecting(Expectation(Call(this, "takesBar", Seq(differentBarParameter)), "second"))

(expectingBoth.value.takesBar(barParameter) must_== "first") and (expectingBoth.value.takesBar(differentBarParameter) must_== "second")
}

def stubWithDifferentMixedArgs = {
val stub = new StubDouble[Foo]
val barParameter = new Bar {}
val differentBarParameter = new Bar {}
val yetAnotherBarParameter = new Bar {}

val expectingFirst = stub.expecting(Expectation(Call(this, "takesBar", Seq(barParameter)), "first"))
val expectingSecond = expectingFirst.expecting(Expectation(Call(this, "takesBar", Seq(===(differentBarParameter))), "second"))
val expectingAllThree = expectingSecond.expecting(Expectation(Call(this, "takesBar", Seq(yetAnotherBarParameter)), "third"))

(expectingAllThree.value.takesBar(barParameter) must_== "first") and
(expectingAllThree.value.takesBar(differentBarParameter) must_== "second") and
(expectingAllThree.value.takesBar(yetAnotherBarParameter) must_== "third")
}

}

0 comments on commit e2af43e

Please sign in to comment.