Skip to content

Commit

Permalink
worked through unit test issues wrt inconsistent message passing beha…
Browse files Browse the repository at this point in the history
…vior. Test worked in isolation but would not work among other tests. Issue was ActorSystem in module wasn't being updated during initialization. fix was to use a def rt a val.
  • Loading branch information
Damon Rolfs committed Sep 28, 2014
1 parent 6571658 commit 72a6926
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 125 deletions.
96 changes: 57 additions & 39 deletions examples/src/test/scala/blog/post/PostModuleSpec.scala
Original file line number Diff line number Diff line change
@@ -1,78 +1,96 @@
package blog.post

import akka.testkit.{TestProbe, TestActorRef, TestKit}
import akka.testkit.{TestActorRef, TestProbe}
//import contoso.conference.registration.OrderModule
import demesne._
import demesne.testkit.{AggregateRootSpec, DemesneModuleFixture}
import org.scalatest.{Suite, Tag}
import demesne.testkit.AggregateRootSpec
import org.scalatest.Tag
import peds.akka.envelope.Envelope
import peds.akka.publish.ReliableMessage
import peds.commons.log.Trace
import sample.blog.post.PostModule.PostState
import sample.blog.post._

import scala.concurrent.duration._

trait PostFixture extends DemesneModuleFixture { outer: Suite with TestKit =>
def trace: Trace[_]
/**
* Created by damonrolfs on 9/18/14.
*/
class PostModuleSpec extends AggregateRootSpec[PostModuleSpec] {

override val module: AggregateRootModule = new PostModule { }
private val trace = Trace[PostModuleSpec]

override val context: Map[Symbol, Any] = {
val result = super.context
val makeAuthorListing = () => trace.block( "makeAuthorList" ){ testActor }
// val makeAuthorListing: () => ActorRef = () => { ClusterSharding(system).shardRegion(AuthorListingModule.shardName) }
result + ( 'authorListing -> makeAuthorListing )
}
}
override type Fixture = PostFixture

class PostFixture extends AggregateFixture {
private val trace = Trace[PostFixture]

/**
* Created by damonrolfs on 9/18/14.
*/
class PostModuleSpec extends AggregateRootSpec[PostModuleSpec]( testkit.system ) with PostFixture {
val probe: TestProbe = TestProbe()

override def module: AggregateRootModule = new PostModule { }

override val trace = Trace[PostModuleSpec]
override def context: Map[Symbol, Any] = {
val result = super.context
val makeAuthorListing = () => trace.block( "makeAuthorList" ){ probe.ref }
// val makeAuthorListing: () => ActorRef = () => { ClusterSharding(system).shardRegion(AuthorListingModule.shardName) }
result + ( 'authorListing -> makeAuthorListing )
}
}

override def createAkkaFixture(): Fixture = new PostFixture

object WIP extends Tag( "wip" )
object ADD extends Tag( "add" )
object HAPPY extends Tag( "happy" )
object NOACTION extends Tag( "no-action" )

"Post Module should" should {
"add content" taggedAs( WIP ) in {
val author = TestProbe()
"add content" taggedAs( WIP, ADD ) in { fixture: Fixture =>
import fixture._

val id = PostModule.nextId
val content = PostContent( author = "Damon", title = "Add Content", body = "add body content" )
val real = TestActorRef[PostModule.Post](
PostModule.Post.props(
meta = PostModule.aggregateRootType,
authorListing = author.ref
)
).underlyingActor
real receive AddPost(id, content)
real.state shouldBe PostState( id = id, content = content, published = false )
author.expectMsgPF( hint = "PostAdded event (ignored in practice)" ) {
case ReliableMessage( _, Envelope( payload: PostAdded, _ ) ) => payload.content shouldBe content
val post = PostModule aggregateOf id
post ! AddPost( id, content )
// probe.expectNoMsg()
probe.expectMsgPF( max = 800.millis, hint = "post added" ) {
// case x => fail( s"recd $x" )
case ReliableMessage( _, Envelope( payload: PostAdded, _ ) ) => payload.content mustBe content
}
}

"follow happy path" in {
"not respond before added" taggedAs( NOACTION ) in { fixture: Fixture =>
import fixture._

val id = PostModule.nextId
val post = PostModule aggregateOf id
post ! ChangeBody( id, "dummy content" )
post ! Publish( id )
probe.expectNoMsg( 200.millis )
}

"follow happy path" taggedAs( HAPPY ) in { fixture: Fixture =>
import fixture._

val id = PostModule.nextId
val content = PostContent( author = "Damon", title = "Test Add", body = "testing the post add command" )
val content = PostContent( author = "Damon", title = "Test Add", body = "testing happy path" )

PostModule.aggregateOf( id ) ! AddPost( id, content )
PostModule.aggregateOf( id ) ! ChangeBody( id, "new content" )
PostModule.aggregateOf( id ) ! Publish( id )

expectMsgPF() {
case ReliableMessage( 1, Envelope( payload: PostAdded, _) ) => payload.content shouldBe content
probe.expectMsgPF() {
case ReliableMessage( 1, Envelope( payload: PostAdded, _) ) => payload.content mustBe content
}

expectMsgPF() {
case ReliableMessage( 2, Envelope( payload: BodyChanged, _) ) => payload.body shouldBe "new content"
probe.expectMsgPF() {
case ReliableMessage( 2, Envelope( payload: BodyChanged, _) ) => payload.body mustBe "new content"
}

expectMsgPF() {
probe.expectMsgPF() {
case ReliableMessage( 3, Envelope( PostPublished( pid, _, title ), _) ) => {
pid shouldBe id
title shouldBe "Test Add"
pid mustBe id
title mustBe "Test Add"
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/main/scala/demesne/AggregateRootModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ trait AggregateRootModuleCompanion extends LazyLogging {
model.aggregateOf( rootType = aggregateRootType, id = effId )
}

//DMR: I don't like this var but need to determine how to supply system to aggregateRootType, esp in regular actors
implicit lazy val system: ActorSystem = {
//DMR: although there is an assumption of each Aggregate existing in only one ActorSystem in a JVM,
// these context properties must be def's rather than val's in order to support testing using multiple isolated
// fixtures.
//DMR: I don't like this def but need to determine how to supply system to aggregateRootType, esp in regular actors
implicit def system: ActorSystem = {
_context get demesne.SystemKey map { _.asInstanceOf[ActorSystem] } getOrElse ActorSystem()
}

implicit lazy val model: DomainModel = {
def model: DomainModel = {
_context get demesne.ModelKey map { _.asInstanceOf[DomainModel] } getOrElse DomainModel()
}

lazy val factory: ActorFactory = {
def factory: ActorFactory = {
_context get demesne.FactoryKey map { _.asInstanceOf[ActorFactory] } getOrElse demesne.factory.systemFactory
}

Expand Down
6 changes: 4 additions & 2 deletions src/main/scala/demesne/AggregateRootRef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ package demesne

import akka.actor.{ActorSystem, ActorContext, ActorPath, ActorRef}
import peds.akka.envelope._
import peds.commons.log.Trace


//todo: DO I really need this class? roottype and id aren't used anywhere? why not return ActorRef from DomainModel
//todo: since enveloping handles message wrap
case class AggregateRootRef( rootType: AggregateRootType, id: Any, underlying: ActorRef ) extends Enveloping {
val trace = Trace[AggregateRootRef]
def path: ActorPath = underlying.path
override def pathname: String = path.name

def tell( message: Any, sender: ActorRef ): Unit = underlying.send( message )( sender )
def tell( message: Any, sender: ActorRef ): Unit = trace.block( s"tell(${message}, ${sender})" ) { underlying.send( message )( sender ) }
final def !( message: Any )( implicit sender: ActorRef = ActorRef.noSender ): Unit = tell( message, sender )
def forward( message: Any )( implicit context: ActorContext ): Unit = underlying sendForward message
def forward( message: Any )( implicit context: ActorContext ): Unit = trace.block( s"forward(${message})" ) { underlying sendForward message }
}

object AggregateRootRef {
Expand Down
39 changes: 26 additions & 13 deletions src/main/scala/demesne/DomainModel.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package demesne

import akka.actor.{ ActorRef, ActorSystem }
import peds.commons.log.Trace
import akka.actor.{ActorRef, ActorSystem}
import demesne.factory._
import peds.commons.log.Trace


trait DomainModel {
def basename: String
def name: String
def system: ActorSystem
def aggregateOf( rootType: AggregateRootType, id: Any ): AggregateRootRef = aggregateOf( rootType.name, id )
def aggregateOf( name: String, id: Any ): AggregateRootRef
Expand All @@ -15,6 +15,8 @@ trait DomainModel {
}

object DomainModel {
val trace = Trace[DomainModel.type]

trait Provider {
def model: DomainModel
def system: ActorSystem
Expand All @@ -25,20 +27,27 @@ object DomainModel {
}


def apply( basename: String = "domain.model" )( implicit system: ActorSystem ): DomainModel = {
modelRegistry.get( basename ) getOrElse {
val result = new DomainModelImpl( basename )
modelRegistry += ( basename -> result )
result
}
def apply( name: String = "domain.model" )( implicit system: ActorSystem ): DomainModel = trace.block( s"(${name}, ${system})" ) {
val k = Key( name, system )
modelRegistry.getOrElse(
k,
{
val result = new DomainModelImpl( name )
modelRegistry += k -> result
trace( s"domain model registry = ${modelRegistry}" )
result
}
)
}

def unapply( dm: DomainModel ): Option[(String)] = Some( dm.basename )
def unapply( dm: DomainModel ): Option[(String)] = Some( dm.name )

private[this] case class Key( name: String, system: ActorSystem )

private[this] var modelRegistry: Map[String, DomainModel] = Map()
private[this] var modelRegistry: Map[Key, DomainModel] = Map()

private case class DomainModelImpl(
override val basename: String
override val name: String
)(
implicit override val system: ActorSystem
) extends DomainModel {
Expand All @@ -53,7 +62,7 @@ object DomainModel {
trace( s"id = $id" )
trace( s"""aggregateTypeRegistry = ${aggregateTypeRegistry.mkString("[", ",", "]")} """ )

if ( aggregateTypeRegistry.contains( name ) ) {
if ( aggregateTypeRegistry contains name ) {
val (rootType, aggregateRepository) = aggregateTypeRegistry( name )
AggregateRootRef( rootType, id, aggregateRepository )
} else {
Expand All @@ -62,6 +71,8 @@ object DomainModel {
}

override def registerAggregateType( rootType: AggregateRootType, factory: ActorFactory ): Unit = trace.block( "registerAggregateType" ) {
trace( s"DomainModel.name= $name" )
trace( s"DomainModel.system= $system" )
trace( s"rootType = $rootType" )
trace( s"factory = ${factory}" )

Expand All @@ -76,5 +87,7 @@ object DomainModel {
}

override def shutdown(): Unit = system.shutdown()

override def toString: String = s"DomainModelImpl(name=$name, system=$system)"
}
}

0 comments on commit 72a6926

Please sign in to comment.