Reputation: 4685
I have an actor, that asks other actor and future can fallbacks to other:
class Subject(db: ActorRef) extends Actor with ActorLogging {
implicit val timeout = Timeout(1 seconds)
implicit val ec = context.system.dispatcher
override def preStart(): Unit = {
db ? "auth" fallbackTo Future {
"auth:timeout_error"
} pipeTo self
}
def receive: Receive = {
case msg: String => log.info(msg)
}
}
I need to test fallback behavior, but don't know how to do it:
class ActorSpec extends TestKit(ActorSystem("MySpec"))
with ImplicitSender with WordSpecLike with BeforeAndAfterAll with BeforeAndAfterEach with Matchers {
val db = TestProbe()
db.ref ! PoisonPill
//db not exist anymore
val subject = system.actorOf(Props(new Subject(db.ref)))
//Something like: subject should receive "auth:timeout_error"
}
How to carry out this task properly?
Upvotes: 1
Views: 158
Reputation: 17973
The easiest way to perform the testing would be to refactor your Subject
class to add a level of abstraction to the db
parameter. There is nothing in Subject
that inherently depends on the fact that db is an ActorRef
; Subject
simply needs something to send a query String
to and receive a Future[String]
response. Therefore you can make the constructor more generic by taking in a function:
object Subject {
type Query = String
type DBResult = Future[String]
type DB : (Query) => DBResult
val defaultAuth : DBResult = Future.successful("auth:timeout_error")
val authQuery : Query = "auth"
def queryWithDefault(db : DB, default : DBResult = defaultAuth) : DB =
(query : Query) => db(query) fallbackTo default
}//end object Subject
class Subject(db : Subject.DB) extends Actor with ActorLogging {
override def preStart() : Unit = {
db(Subject.authQuery) pipeTo self
}
override def receive : Receive = {
case msg : String => log info msg
}
}//end class Subject
You can now test the queryWithDefault
function without having to use akka at all:
import org.scalatest.{Matchers, WordSpecLike}
import org.scalatest.concurrent.ScalaFutures
class SubjectSpec
extends Matchers
with WorkSpecLike
with ScalaFutures {
val alwaysFail : DB =
(query : Query) => Future.failed(new Exception("always fails"))
import Subject.{defaultAuth, queryWithDefault, authQuery}
"queryWithDefault" should {
"always return default when db fails" in {
val db = queryWithDefault(alwaysFail, defaultAuth)
whenReady(
for {
authQueryRes <- db(authQuery)
fooQueryRes <- db("foo")
defaultRes <- defaultAuth
}) {
authQueryRes shouldEqual defaultRes
fooQueryRes shouldEqual defaultRes
}
}//end "always return..."
}//end "queryWithDefault" should
}//end class SubjectSpec
You can then use the refactored, and unit tested, Subject.queryWithDefault
function in production:
val actorDB : DB = (query : Query) => (db.ref ? query).mapTo[String]
val subject = system.actorOf(Props(queryWithDefault(actorDB, defaultAuth)))
Upvotes: 1
Reputation: 11
You can override receive method and send message to your TestProbe()
val probe = TestProbe()
db.ref ! PoisonPill
val actor = system.actorOf(Props(new Subject(db.ref) {
override def receive: Received = {
case message => probe.ref ! message
}
}))
probe.expectMsg("auth:timeout_error")
Upvotes: 1