Skip to content

Instantly share code, notes, and snippets.

@ShahOdin
Last active August 2, 2017 11:16
Show Gist options
  • Save ShahOdin/ae6476ae5c0b672b2a838c4d06a22450 to your computer and use it in GitHub Desktop.
Save ShahOdin/ae6476ae5c0b672b2a838c4d06a22450 to your computer and use it in GitHub Desktop.
/*
* In a service-based architecture, often multiple components have common dependencies. However, each is usually interested in
* a fraction of the provided functionality in a dependency. As such, when testing a dependency, the components tend to mock the individual
* functionalities in place (in their spec file). This means that one usually ends up with a lot of mock duplication in spec files.
* This is bad because these are hard to maintain. If a service's API were to change, the spec files mocking a functionality could
* fail and one has to go through the tests on a one by one basis and update them individually. Moreover, this duplication means
* that a high level component, needs to potentially combine the provided mocks if one of its dependencies shared a dependency with itself.
* Below is a demonstration of a suggested way service components (http-services, actors, etc.) should mock their dependencies.
* They need to define a MockAPI which is a list of components they need, in order to provide their functionality.
* In essence, the responsiblity of offering a test API is given to the developer of a service and other services depending
* on this component will pick and choose the mocks for their dependencies.
*/
/******************************************************************************************************
* The Mock API for an actor.
*/
//this will be mixed in by mocking behaviours
trait Mocking {
import akka.actor.Actor.Receive
def receive: Receive
def sender: ActorRef
}
trait Actor1MockAPI{
//injected parameters, say constructor parameters
def actor2
def actor3
def actor4
}
trait MockBehaviour2A extends Actor2MockAPI with Mocking{
private def behaviourOne: Receive = ???
private def behaviourTwo: Receive = ???
abstract override def receive: Receive = super.receive orElse behaviourOne orElse behaviourTwo
}
trait MockBehaviour3A extends Actor3MockAPI with Mocking{
private def behaviourAlpha: Receive = ???
private def behaviourBeta: Receive = ???
abstract override def receive: Receive = super.receive orElse behaviourAlpha orElse behaviourBeta
}
class Actor1Test extends Actor1MockAPI{
//provide dependencies
def actor2 = ...[with MockBehaviour2A with MockBehaviour2B with MockBehaviour2C]
def actor3 = ...[with MockBehaviour3A]
def actor4 = TestProbe() //eg. a dependency used in the actual implementation of Actor1 which can be left behaviourless.
val actor = Actor1.props(actor2, actor3, actor4, ...)
tests{...}
}
/******************************************************************************************************
* The Mock API for a servicce.
*/
// a service, say a Http Service, would simply be:
trait service1MockAPI {
//injected parameters, say constructor parameters
def actor2
def actor3
}
class Service1Test extends service1MockAPI{
//provide dependencies
def actor2 = ...[MockBehaviour2A with MockBehaviour2B with MockBehaviour2C]
def actor3 = ...[with MockBehaviour3A]
}
/******************************************************************************************************
* turning the mock traits into autoPiloted test actors:
*/
//recall:
trait Mocking{
import akka.actor.Actor.Receive
def receive: Receive
def sender: ActorRef
}
trait Mock1 extends Mocking{ //ignore the extends ActorXMockAPI for the time being
import akka.actor.Actor.Receive
abstract override def receive: Receive = ??? //can involve refrences to sender which is not yet provided
}
trait Mock2 extends Mocking{ //ignore the extends ActorXMockAPI for the time being
import akka.actor.Actor.Receive
abstract override def receive: Receive = ??? //can involve refrences to sender which is not yet provided
}
// we define:
def autoPilot[M <: Mocking]: TestActor = {
//need this because the mocks are not responsible for providing the sender.
val mockMaker(testProbeSender: ActorRef): Mocking = {
object Mock extends M {
sender = testProbeSender
}
}
new TestActor.AutoPilot {
def run(sender: ActorRef, msg: Any): TestActor.AutoPilot = {
mockMaker(sender).receive.apply(msg)
TestActor.KeepRunning
}
}
}
//usage:
val autoPilotedActor: TestActor = autoPilot[Mock1 with Mock2]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment