Skip to content

Commit

Permalink
Introduced TestDouble abstraction to integrate
Browse files Browse the repository at this point in the history
the new macro-based stubbing with the rest of Goose.
  • Loading branch information
rafaeldff committed Sep 30, 2012
1 parent 87fcd48 commit e75a06c
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 63 deletions.
7 changes: 7 additions & 0 deletions src/main/scala/net/rafaelferreira/goose/CheckHelpers.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package net.rafaelferreira.goose

trait CheckHelpers {self: GooseStructure =>
import PartialFunction._

def reportMissing(vs: Seq[TestDouble[_]]) = {
val missing = vs.zipWithIndex.filterNot {cond(_){case (InitializedDouble(_), i) => true}}
"No value was supplied for dependencies %s. Did you forget 'when' or 'and' clauses?" format (missing.mkString("[", ",", "]"))
}

def whenAllPresent[R](vs: Seq[TestDouble[Any]])(result: => R) = {
val missingValues = vs.zipWithIndex.collect {case (UninitializedDouble, i) => i}
missingValues match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,40 @@ import org.specs2.Specification

trait CheckingForVariousArities extends CheckHelpers {self: GooseStructure with Specification =>
def check[T1: ClassTag, R](resultExpression: (T1) => R)(testDefinition: (Dependency[T1]) => When[R] => When[R]): Fragments = {
val (dep1) = (dep[T1])
val (dep1) = (dep[T1]("1"))
val calcResult = {state:State =>
val (value1) = (state.get(dep1))
whenAllPresent(Seq(value1)) { resultExpression(value1.get) }
(state.get(dep1)) match {
case (InitializedDouble(value1)) => Right(resultExpression(value1))
case (double1) => Left(reportMissing(Seq(double1)))
}
}

val when = new When[R](calcResult)
testDefinition(dep1)(when).results
}

def check[T1: ClassTag, T2: ClassTag, R](resultExpression: (T1, T2) => R)(testDefinition: (Dependency[T1], Dependency[T2]) => When[R] => When[R]): Fragments = {
val (dep1, dep2) = (dep[T1], dep[T2])
val (dep1, dep2) = (dep[T1]("1"), dep[T2]("2"))
val calcResult = {state:State =>
val (value1, value2) = (state.get(dep1), state.get(dep2))
whenAllPresent(Seq(value1, value2)) { resultExpression(value1.get, value2.get) }
(state.get(dep1), state.get(dep2)) match {
case (InitializedDouble(value1), InitializedDouble(value2)) => Right(resultExpression(value1, value2))
case (double1, double2) => Left(reportMissing(Seq(double1, double2)))
}
}

val when = new When[R](calcResult)
testDefinition(dep1, dep2)(when).results
}

def check[T1: ClassTag, T2: ClassTag, T3: ClassTag, R](resultExpression: (T1, T2, T3) => R)(testDefinition: (Dependency[T1], Dependency[T2], Dependency[T3]) => When[R] => When[R]): Fragments = {
val (dep1, dep2, dep3) = (dep[T1], dep[T2], dep[T3])
def check[T1: ClassTag, T2: ClassTag, T3: ClassTag, R](resultExpression: (T1, T2, T3) => R)(testDefinition: (Dependency[T1], Dependency[T2], Dependency[T3]) => When[R] => When[R]): Fragments ={
val (dep1, dep2, dep3) = (dep[T1]("1"), dep[T2]("2"), dep[T3]("3"))
val calcResult = {state:State =>
val (value1, value2, value3) = (state.get(dep1), state.get(dep2), state.get(dep3))
whenAllPresent(Seq(value1, value2, value3)) { resultExpression(value1.get, value2.get, value3.get) }
(state.get(dep1), state.get(dep2), state.get(dep3)) match {
case (InitializedDouble(value1), InitializedDouble(value2), InitializedDouble(value3)) => Right(resultExpression(value1, value2, value3))
case (double1, double2, double3) => Left(reportMissing(Seq(double1, double2, double3)))
}
}

val when = new When[R](calcResult)
testDefinition(dep1, dep2, dep3)(when).results
}

}
38 changes: 22 additions & 16 deletions src/main/scala/net/rafaelferreira/goose/Goose.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import org.specs2.specification.Fragment
import org.specs2.execute.Failure
import scala.reflect.ClassTag

trait TestDouble[+T]
object UninitializedDouble extends TestDouble[Nothing]
trait InitializedDouble[T] extends TestDouble[T] {
def value: T
}
object InitializedDouble {
def unapply[T](double: InitializedDouble[T]) = Some(double.value)
}

trait GooseStructure {this: Specification =>
import scala.collection.immutable.Map

Expand All @@ -36,16 +45,8 @@ trait GooseStructure {this: Specification =>
}
}

//type DepGen[T] = Option[T] => Option[T]

trait TestDouble[+T] { def get:T = sys.error("should never happen") }

object UninitializedDouble extends TestDouble[Nothing]
trait InitializedDouble[T] extends TestDouble[T] {
def value: T
override def get = value
}
//class StubDouble[T] extends InitializedDouble

case class DirectDouble[T](value:T) extends InitializedDouble[T]

class State(assumptions: Map[GeneralDependency[_], TestDouble[_]] = Map().withDefaultValue(UninitializedDouble)) {
Expand All @@ -58,10 +59,15 @@ trait GooseStructure {this: Specification =>
new State(assumptions + (dep -> newDouble))
}

def getDouble[T](dep:GeneralDependency[T]):TestDouble[T] = assumptions(dep).asInstanceOf[TestDouble[T]]
def getDouble[T](dep:GeneralDependency[T]):TestDouble[T] =
assumptions(dep).asInstanceOf[TestDouble[T]]

def get[T](dep: GeneralDependency[T]): TestDouble[T] = {
val res = getDouble(dep)
res
}

def get[T](dep: GeneralDependency[T]): TestDouble[T] =
getDouble(dep)
override def toString = "State("+assumptions.toString+")"
}

type ResultExpression[R] = Either[String,R]
Expand Down Expand Up @@ -95,17 +101,17 @@ trait GooseStructure {this: Specification =>

type Dependency[T] <: GeneralDependency[T]

def dep[T: ClassTag]: Dependency[T]
def dep[T: ClassTag](name:String): Dependency[T]
}


trait Goose extends GooseStructure with CheckingForVariousArities with stubs.Stubs {self: Specification =>
class ActualDependency[T: ClassTag] extends GeneralDependency[T] with DirectDependency[T] with StubDependency[T] {self =>
class ActualDependency[T: ClassTag](name:String) extends GeneralDependency[T] with DirectDependency[T] with StubDependency[T] {self =>
val manifest = implicitly[ClassTag[T]]
override def toString = "DEP[%s]" format result
override def toString = "DEP[%s]" format name
}

type Dependency[T] = ActualDependency[T]

def dep[T: ClassTag]: ActualDependency[T] = new ActualDependency[T]
def dep[T: ClassTag](name:String): ActualDependency[T] = new ActualDependency[T](name)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@ import org.specs2.matcher.Matcher
import scala.language.experimental.macros
import scala.reflect.ClassTag

/* TODO: extend Double */
case class Stub[T: ClassTag](expectations: Seq[Expectation[T]] = Vector()) {
case class StubDouble[T: ClassTag](expectations: Seq[Expectation[T]] = Vector()) extends InitializedDouble[T] {
lazy val results =
expectations.foldLeft(Map[String, AnyRef]()) {(map, expectation) =>
map + (expectation.methodCalled -> expectation.result)
}

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

def stubObject: T =
def value: T =
ProxyFactory { (obj:Object, method:Method, args:Array[Object]) =>
results(method.getName)
}
Expand Down Expand Up @@ -53,14 +52,14 @@ trait FakeArgument {

trait StubsStructure {

implicit def argThat[T: ClassTag, U <: T](m: Matcher[U]): T = {
/*implicit def argThat[T: ClassTag, U <: T](m: Matcher[U]): T = {
val parameterClass = implicitly[ClassTag[T]].runtimeClass
ProxyFactory.make(parameterClass, classOf[FakeArgument]) {(obj:AnyRef, method:Method, args:Array[AnyRef]) =>
method.getName match {
case "$gooseMatcherMethod" => m
case _ => null
}
}
}
}*/

}
30 changes: 10 additions & 20 deletions src/main/scala/net/rafaelferreira/goose/stubs/Stubs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,20 @@ import scala.language.experimental.macros
trait Stubs { this: GooseStructure =>
private[Stubs] val mocker = new org.specs2.mock.MockitoMocker {}

class Stubbing[T,R](dependency: GeneralDependency[T])(call: T => R) {
def ==>(r: R) = new Assumption[T] {
def relatedTo = dependency
def apply(previous: TestDouble[T]) = UninitializedDouble

//{
//val mock = previous match {
//case None => mocker.mock(manifest)
//case Some(mock) => mock
//}
//mocker.when(call(mock)).thenReturn(r)
//Some(mock)
//}
}
}

case class ReturnAssumptionFactory[T](call:Call[T]) {
def ==>(result:Any):Assumption[T] = new Assumption[T] {
class ReturnAssumptionFactory[T: ClassTag](call:Call[T]) {
def ==>(result:AnyRef):Assumption[T] = new Assumption[T] {
def relatedTo = call.context.asInstanceOf[GeneralDependency[T]]
def apply(td:TestDouble[T]): TestDouble[T] = UninitializedDouble
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 _ => sys.error("Cannot mix stub with non-stub expectations.")
}
}
}

implicit def call2returnAssumptionFactory[T](c:Call[T]) = new ReturnAssumptionFactory[T](c)
implicit def call2returnAssumptionFactory[T: ClassTag](c:Call[T]):ReturnAssumptionFactory[T] =
new ReturnAssumptionFactory[T](c)

trait StubDependency[T] { self: GeneralDependency[T] =>
val manifest: ClassTag[T]
Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/net/rafaelferreira/goose/GooseSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class GooseSpec extends Specification with ResultMatchers with Goose {
def is = "variable assumption passing" ^ e1 ^
"variable assumption failing" ^ e2 ^
"stub assumption passing" ^ e3 ^
"stub assumption failing" ^ e4 ^
"stub assumption failing" ^ e4 ^
"mixed assumptions passing" ^ e5 ^
"variable assumption overriding" ^ e6 ^
"mock assumption overriding" ^ e7 ^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ package sample
import org.specs2.Specification

class AddressMapperSpec extends Specification with Goose {
def is = "Street mapping" ^`street mapping` ^
def is = pending /*"Street mapping" ^`street mapping` ^
"Address mapping" ^`address mapping` ^
end
end*/

def `address mapping` = check(new DatabaseBackedAddressMapper(_:Database).mapAddress(_:String)) {(database, addressId) =>
_.when(addressId ==> "123").
Expand Down
12 changes: 6 additions & 6 deletions src/test/scala/net/rafaelferreira/goose/stubs/StubsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ class StubsSpec extends Specification with StubsStructure {
"stub a method that may take different arguments" ! stubWithDifferentArgs

def stubNoArgs = {
val stub = new Stub[Foo]
val stub = new StubDouble[Foo]
val expecting = stub.expecting(Expectation(Call(this, "foo", Nil), "result"))
expecting.stubObject.foo must_== "result"
expecting.value.foo must_== "result"
}

def stubWithArgs = {
val stub = new Stub[Foo]
val stub = new StubDouble[Foo]
val barParameter = new Bar {}
val expecting = stub.expecting(Expectation(Call(this, "takesBar", Seq(===(barParameter))), "result"))
expecting.stubObject.takesBar(barParameter) must_== "result"
expecting.value.takesBar(barParameter) must_== "result"
}

def stubWithDifferentArgs = {
val stub = new Stub[Foo]
val stub = new StubDouble[Foo]
val barParameter = new Bar {}
val expectingFirst = stub.expecting(Expectation(Call(this, "takesBar", Seq(===(barParameter))), "result"))
expectingFirst.stubObject.takesBar(barParameter) must_== "result"
expectingFirst.value.takesBar(barParameter) must_== "result"
}

}

0 comments on commit e75a06c

Please sign in to comment.