Last active
August 2, 2017 11:16
-
-
Save ShahOdin/ae6476ae5c0b672b2a838c4d06a22450 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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