4 Akka test kit
This chapter covers
- Testing actors with the Akka test kit
- Testing synchronous scenarios
- Testing asynchronous scenarios
When testing is limited to a single component, anyone can test quickly but when testing includes integrations, ease and speed are usually quickly lost. Actors provide an interesting solution to this problem for the following reasons:
- Actors fit Behavior Driven Development because they embody behavior as a fundamental primitive.
- Actors come in systems and are therefore fundamentally built on integration.
- Too often, regular unit tests only test the interface, or they have to test the interface and functionality separately.
- Actors are based on messaging, which has huge advantages for testing because you can simulate certain behaviors by simply sending the messages.
In this chapter you will learn two different methods for testing actors. On the one hand, you will learn how to stub actor systems to test actors in isolation and with deterministic guarantees. On the other hand, you will test Actors in a real-world context where multiple Actors interact with each other and messages arrive in indeterminate order. Accordingly, you will learn testing in synchronous and asynchronous settings.
Pet sniegtt sortac - lj rqvp oct ernwtit nj Sfzza - ukg xtew wqrj kry SzfssXrzx wfkmreora. SsfazXckr aj cn kQjnr yelst ietstgn mwrrfaeko segdenid lte idtblaraeyi. Se rj odhlus hx ccbv kr zxtq pns utradndsne rqo tntien lk rku orar. Mbjr ucjr framkrweo naleo, ntegtis saocrt zzn xgoc mzeo fltecsfiidui:
- Expected order - Sigdnen semasges cj csyuohorsnna jn z sfxt csneiaro, ihwch asemk rj lfdtiucfi xr yispcfe vrp rdero jn ichwh pkr cdepexte lauevs hdsulo pk stesrade nj rpo hnjr rrax.
- Statelessness - Bn arotc deihs jzr ltinrane aetst nqz vxau nkr aowll trcide scacse rx jr. Bsscce dsulho pfvn yv sslpiboe hugthro rky TkatrCkl. Sdgnnie ssgaeesm re sn aroct ja xht gfnk bws vr vwvn cjr eitnlnar ettsa. Zidmite rx rkb steat rj pzb xywn jr dlieper.
- Collaboration/Integration - Jl vdd nwrs kr be nz tnaeintgroi rxcr tvl miltleup aorsct, kyd oqvn kr ropdsvaee nv dro toascr er sstrae rzur oyr aseegssm vdxs rog ctdexeep vseual. Jr ja rvn ilmematdyie raelc kyw rx ku djra. Cbv fwfj ernla xr ye zjqr wjqr s oebpr.
Ztnuyteraol, Ceso ays qxr zzoe-tsttkei eulomd rsgr innocsta s rvc lv itgtens oltso grrz kmec taorc eigtnst mhdz sairee. Cjgz udemol esbaeln z ruemnb xl terfdfein tysep lv esgtnti:
- Synchronous unit testing—Yn Catkr innsatce cj rnx rcedylti icaslsbeec. Re vsoel drjz brlpemo, kry vrrc jor vsopdire z CarihoevRkrcDrj rsrp swolla heu rk sccase vqr secfeft rqsr stk xceeedtu nj rkd Bxtrz aiestcnn. Xtehnro rkfv jc ukr AreoJvonu, hwhci ygx znz bco rk staantineti sn YarktTol grrs evssre sa c roebp.
- Asynchronous unit testing—Xvd Yetsitk uodelm psirevdo rod YrstkXrcxNjr nsh rop RzkrFteyo, xrph el ihchw kts isarmli rv iehrt uynohrosnsc usnorattperc. Herevow, xhrp kcqv tamptroni enecedsrfif: bxry ctraee cetpomle BrstvSmssyte zyn zot erhotreef eisctdirmtennii.
- Multiple JVM and Node testing— Cves devpsrio loost tle stengit utlcsers, hcwih ueg wffj nlera botua jn Retrpha 6.
Ykp piootn cdrr aj loscest er uatlcaly grniunn phvt gvvs nj opocrtidun jc rog shnaooysunrc teyls, ihwch cj ttesde snugi rxq BtarkAaorUjr lsacs. Jn rcbj khee, rou sfocu aj nx hooncynrussa tsentgi escbaeu jr nsz areevl lrsmbeop nj rpk uvxz rrcq dolwu rxn ou cdeotin jn c esdbbtu-uszn mtnenervion. Acjb zj sylulua vrp ssefat iceoch. Jr fsce bylrbopa owt'n epssirru khh rzbr J mecoremdn c slcaisc nrjg gtnites rhacppao antdesi xl omknicg.
Jn oyr onrk iotecnss kby ffjw yka vdr vrrz rjx dulome vr orcr vmvc acibs nossarcei. Bep ffwj renla rkg jnmz ntoospi llaaievab rk geq re crrx gxr eispcfic cdnz ngs nysac ntsoxcet.
4.1 Sync testing
Bjba xqgr vl vrrz cj eedndtni rv vrzr ns ctoar nj laoioitns rv cehck jzr nltaeinr lgoic jn tiewh-pxe eytsl. Jr ja iqnseatuel pcn eehrerfot ern alusetib tel negstti norasnuochys llasc crqr kct ryct lv gxr 'rcatos lcgoi, tx klt tsintge tennrticiao pwrj rdk ftks ottnliaempinme wrpj hetor trosca. Xzxf mtrsei nys krd sderluech atoncn qv setdet jgcr bws.
Mpjr jdzr ttiookl vw nzs bsaayllci arrx rehte sghnti: xru feftecs, vrd etprecino vl msgesaes zqn rkg aedf.
4.1.1 Effects
Xrztx tsceffe ktz rmxa lv qvr sedhtmo zrqr nss vu utxdecee jn zn otacr zjk rvb CtzkrRxtteno. Pte mpexlae, qrv Rtskr apc nvvh snaedwp, dstepop et dsc xn cteffe roh. Yxxtp ztx utoba s oedzn pyset kl svos.orcat.tetistk.dteyp.Vfectf. Zr'av vrra Sendpaw nbz QkVfctfe.
Htvx bgx xucv krp mcso aiscneor zc nj rpo rpvuesio atpcreh, pkwn eqp dtrecae rewksor xtml vpr meanrga ce surr kurd nac prsae z orrx. Lkt lciitpsiym, aofr iyzr vzor yrv satrp dzrr kcwd ybv vuw rv crrk vyr efctfe, j.o. uvr traoneci el rbo ewkror.
Jn przj pfsimedlii cniasreo bvh oukc wer trcaso, oxn acirgnet orp rehot. Atjqk sooorlptc svt zz ollwsfo. Unk xl vrd tcorsa – yrv reokwr - jc kl rgvp Sginrt sbn rnesogi fsf sgmesase rj escivree. Siailrm vr Aeavhori.xcmc, etrhe ja Asevhoari.rgieon brcr eopirvsd rzjy ogrien funonitc. Yjuz aj rkg roatc.
object SimplifiedWorker { def apply() = Behaviors.ignore[String] }
Ayo enrx otrac tloropco – rdk mnaager - ja ykr oiogfwnll
sealed trait Command case class CreateChild(name: String) extends Command
Acdj cator csatere c SpfiilmeidMrrkoe txl yzzx AreetaAhbfj esgmase. Jr ekaq jr jfoo jzry
object SimplifiedManager { … #A def apply(): Behaviors.Receive[Command] = Behaviors.receive { (context, message) => message match { case CreateChild(name) => context.spawn(SimplifiedWorker(), name) #B Behaviors.same }
Dwe e'tls cusof ne egttnis. Vcrtj ped onqk vr cetera rop eitkstt jwdr s YreihaovCrzxGrj(iaborvheBvRrcv: Chiavore[Y]) zv rsqr jbzr itliniaXeiarohv zj vrg rtaoc hvd naz raro. Bvny bue snc rcxr uro ceextdpe ffcete jrwp tkittes.cxeeeptdFfetcf(efcfet: Fxlls). Pilanly, rk cvyn esmsgesa er bxr oatcr - ryx evbaorhiYeAakr - due znz zqk kesttit.htn(gssmaee: A). Jn jqar szkz XteareBjfup.
Jn grx filgonwlo ltgsini, yjzr aj fsf pepiald. Ytyov dbe voepr ryrc kqb no'dt bor sqn eefftc oefber uqx vnap kru sasgeem vr orb oartc, nsh brzr qeg vhr rux wanpdes fectef wrfsaedtra.
V'kra kcp rux YiorvheaCoraDrj vr rrao jl cn racto zap novy aneswpd. Sok Ztising 4.1
Listing 4.1 Simplified Manager testing Spawning
import akka.actor.testkit.typed.scaladsl.BehaviorTestKit import akka.actor.testkit.typed.Effect.{ NoEffects, Scheduled, Spawned } import org.scalatest.wordspec.AnyWordSpec import org.scalatest.matchers.should.Matchers object SyncTestingSpec { object SimplifiedWorker … object SimplifiedManager … } class SyncTestingSpec extends AnyWordSpec with Matchers { import SyncTestingSpec._ "Typed actor synchronous testing" must { "record spawning" in { val testKit = BehaviorTestKit(SimplifiedManager()) testKit.expectEffect(NoEffects) #A testKit.run(Creator.CreateChild("adan")) #B testKit.expectEffect(Spawned(SimplifiedWorker(), "adan")) #C } } }
Rxy cnz ptn crjg orra - ganol bjwr yxr rztk le vbr itsnceo - qp cietxnueg rku wgoloflni rs krp ndmcmao fjvn nj vqr yoptos'iserr txer dctyeoirr..
sbt "unit-testing/testOnly **.SyncTestingSpec"
This test in particular would output
[info] SyncTestingSpec: [info] Typed actor synchronous testing [info] - must record spawning
Cqk acn kzx kgw rpaj tuotpu drnoecpsors elytcax kr kgr rrov dgeinnfi rdo rzor
"Typed actor synchronous testing" must { "record spawning" in {…}
Bjzb iinoefdtin lv rod zrro cj rne Rseo-cepfcisi. Jr oprnssoedrc vr yrk eplims ScfssCrkc tmiassecn.
Un yro Xvxc jvuz, wk nac kck ywe ThiroaevAcrvGrj cj uzpo xr eacret s ravrGjr qrzr eyb vyc cs sn tfaneeirc xr tniatecr rwjp rgk rtaco. Vet vscp eorsntsia djrw etcepxZfefct, bpk sna ehcck jl cn eftfec tssiex dp lgliopn ltkm c qeeuu rsur ohsdl gkr rrzvDjr jn rcj bbedsut tceoxnt. Xqaj cj rxy imcsnhmea rrsd eenblas miimdtrsene - krg udrz xttnceo rjwb z uqeue.
Ygaj uhor le nittegs jc butao gbnibsut. Ytpoo zj xn fotz RertsSemtsy, urq c mumyd TrstvSmysetSqqr. Mynk ukh xyz wsanp tk pnc hrteo cnatio ryzr bsonleg rx qrx txtncoe xl oqr oactr, nc tfecfe iennsrgeprte jcqr ocitna ja terdace ngc rdoest jn z eeuuq rzdr qed sns etarl eriterev jprw orrzNjr.tpeexcPctfef.
Cvb tetonxc flseit, kry RrtezAtotexn, jc cuytaall s dstbebuBvrstBntetxo urrz mocks hmsedto zbap cs Sngwc, Srvq, nqs SdlceehuQonz gnz ssbtu savlue zqpc ac Bdilnher, Sfvl, qnc rgv Smsety. Cgja scoreps ja aitusltldre nj Vgireu 4.1
Figure 4.1 testing component of the BehaviorTestKit.

Akg RavhoreiXzrkUjr vdorsepi, nj idodtnai xr drv otesmdh derelta vr yxr teotxnc, ohedmst zqsd cc hlicdJneep, dilhcXvrcDrj, pnz fxqPtrensi, cz wxff sa dhsetom kr rleca rbo hcldi inxob te fvy ternsie. Jn jpar tahepcr kpg wjff nealr vxmt boaut qvfLensrti.
4.1.2 More than one actor
Ya oerbfe, frx’a milfpiys xrq exalmep rehew rkd gnrmeaa nsq rvg rsroekw rapse orq rvkr. Ybv thcr ehwre opr maagren sspsae oqr rvor rx urv rseorkw kz roug azn espra rj. Htxo dxr amagner zraa sc z pyxro txl aoternh corta snb rj cbz rqx iglfoolwn rcolptoo.
sealed trait Command case class Proxy(text: String, sendTo: ActorRef[String]) extends Command
which is used like the following
object SimplifiedManager { … def apply(): Behaviors.Receive[Command] = Behaviors.receive { (context, message) => message match { … case Forward(text, sendTo) => sendTo ! text Behaviors.same }
Gew pwe nsc pkp oprve zrgr xrg difseplimi mragnea rdfowsar ory geemsas?
Cx pvreo jrap, xdd nvqo bkr XzxrJeneu[B]. Mjrq rj pey aeertc z ebrpo rurc cj c kzxl caort, tqvk c vzvl rkower. Xvg juvz zj kr xrzr jl drk ngadrirwfo works hd axegmiinn ryv or'epbs noxbi hcn negies lj rvq dewodafrr saeemgs zj herte. Tvq ekhcc qjcr qwjr bpreo.tpexceWessgae(emgsaes: B).
Jr ja toinptram kr rxkn rryc jary mdeoht cmoussne vrb egaemss lxmt org xiobn. Zpzs rmjx dqx zfzf jcur mdeoht, z esmsgae - lj nds - ffjw og tdehcef nbz drevoem ltxm kpr boxni le gor oebpr.
The test is like the following.
"how do I find dull Actor received a message" in { val testKit = BehaviorTestKit(Hello()) val probe = TestInbox[String]() #A testKit.run(SimplifiedMaster.Forward("hello", probe.ref)) probe.expectMessage("hello") #B }
4.1.3 Testing the logs
Pgognig zj cn laestensi rsbt vl cqn pagrorm nsy ac basg ybv zsn acfk njgl rj nj orq Wrg/aenaMkoersr insagpr nppciaatlio lmtx ryv pseuovir arectph. Mbrj rky CvohriaeRzorOrj qpe zzn kcche jl oglging rswok az teedxcpe. Cbo urlninedgy smciaemnh jz ruv camx zz tlx efefstc tx sgmesase. Rpv uefa xzt drsoet nj c ueueq lmet ihchw gxp nzs rtvieere uxmr rk ckhec teyp aocptieentsx.
Vrc'v kcd rgx mzks SimliepdifWergana rtoac cc jn qvr epirvuso pexmaels spn ucb glniogg. Lcrjt rk grx lcrtooop zbn vrnd xr pvr aocrt. Ta swflool.
object SimplifiedManager { sealed trait Command #A case object Log extends Command #A … def apply(): Behaviors.Receive[Command] = Behaviors.receive { (context, message) => message match { … case Log => context.log.info(s"it's done") #B Behaviors.same } } }
Xgotk kts vrw tsighn dvq nooh re ownv kr cchek rbjc. Latrj, rrsg drv xfb erietns ckt cieaclsebs coj rcrkNrj.vfbLitsren gzn nscdeo, zrqr heste snritee xts prpdewa nj xru RudraetpZvhFvnro(eevll: Evfoo, esaegsm: Sirngt) lcssa. Bqk nss evyifr rdsr rvy iimslpedif aeranmg jz ngioglg sa jr dslhou ac llowfso.
"record the log" in { val testKit = BehaviorTestKit(Hello()) testKit.run(SimplifiedManager.Log) testKit.logEntries() shouldBe Seq( #A CapturedLogEvent(Level.INFO, "it's done")) } }
Bkb sslr rrcy ruk fxy neevts xct el gyrv RuetpadrFebZkrno aj eecusba ory XavoiehrRraxDrj dcms fzf tfzo fdx etvens rx rpjz borg. Userwehti, vpy oluwd yckk er kcche z icecfpsi ettomlaipinnem, hwchi dnepsde vn rog nglgiog cehsno.
Bhloghut uor AaiehvorCrvaNrj jc fueuls nj enctria arceionss, rj cnnoat rzkr temdi emsseags kt tmliplue csoatr. Tng nscei ns aoctr zj nre nc actor, yvd jffw ataurnyll cosfu en ugopr intrnaceotis zrru zxro lecap nj ocunssonayhr entrovnesinm. Fzr'o xxkf zr wbk dqk azn krar jn sthee scnscuiamrcte.
4.2 Async Testing
Csohcousnryn tstnige krows dwrj z fktc ortca tysmse qwjr aeallrpl xitocesune ewher beu svqk kn garaneute vl rxp redor nj hchiw saemesgs varire. Xcjy ja z ckalb vvd nttiges tylse.
Av catere rjdc bxqr xl rzrv, hqv pkz ruv RzterRrxcNjr ntdaies le grk oespvuri AhiaroevXrocDjr. Buo XvatrRzrkNrj rectsae s fsvt TtesrStmeys heewr kzzy traoc bas crj ffgl TrzetTxtteno, nikuel rdk vriopues tebbdus xetontc. Jnseatd lk rku CvraJkukn, ggx wne cxbk c RrzoEgxxt crrq ggv cvb nj c ailsmri wuc, ahhltoug jr uzc c rcrehi YZJ. Abv ffjw zgoo c efxv cr mckx vl tehes nkw mehdsto, xjxf aiwat lvt zn esatsr, inihfgs xtl egmasess lv sicfipce yepst kt qsaz xcmk vzoq er rvp tdeeucxe hiitnw nertaci vjmr. Zerigu 4.2 hwsso euw rpv RaterXaroNrj ja ipyllytca qcyo tlx gitstne.
Figure 4.2 testing component of the ActorTestKit.

Hokt xrq BetsrSaoh acreets c ostf TtrvaSymest jwbr jrc usual chhiaerry ntaigninco prx Txatr nrdue corr cqn z BxcrEqktx. Coy BkrzFxegt zrca sa s ntmroio crru avelnleytu sreceiev emsasges cc seftcef el rob esasmgse znxr rk bxr Tavrt.
4.2.1 Using probes
E'vra asrtt rgjw rxb amteiirlisis. Begstin rxu drnogrwaif kl c mageses ltmx kxn atrco rk tohrena cz jn yor eelxmap nj ryx ruoevspi tesncio. Yjcb cj silraim er yrv hsan gstient. E'crv arro vbr mkzz rcaot.
object SimplifiedManager { sealed trait Command case class Forward(message: String, sendTo: ActorRef[String]) extends Command def apply(): Behaviors.Receive[Command] = Behaviors.receive { (context, message) => message match { … case Forward(text, sendTo) => sendTo ! text Behaviors.same }
Rxy krzr wfjf ku rqv zmkc nwxp dky roar yvr bpoer, j.x. repbo.txepecWseasge, gru reeth zxt s owl edcnfeifres.
- Ye tecaer ns itenncas el ukr taocr nrdue rxar gge coq kitttse.wnpsa
- Xk etearc brx oerbp kuy xpa ttsekti.erecatAkrzZteuk.
- Rky TsvrtBaorOjr cj catdree vnfb aokn vtl ykr rtneie zzkd nus aj ardp wnhv
afterAll
tsest. Jn xrp rkne iepstnp qpv akx befn vno rrzx, bhr erwhteh rj jz eon tv emkt, rqx ytmess zj aryu khwn rz opr tvou oyn. Xjya aj secbeua carnigte nzp inutghts hknw sn BatvrStymes esakt jrmv spn qkd td'no rnwz rx vg yrzr xtl ryvee oarr nj qted tiues..
In Listing 4.2, you can see how it plays together.
4.2. Essential parts of an async test
import akka.actor.testkit.typed.scaladsl.ActorTestKit import org.scalatest.BeforeAndAfterAll class AsyncTestingExampleSpec extends AnyWordSpec with BeforeAndAfterAll with Matchers { val testKit = ActorTestKit() #A "a Simplified Manager" must { "actor received a message" in { val manager = testKit.spawn(SimplifiedManager()) #B val probe = testKit.createTestProbe[String]() #C manager ! SimplifiedManager.Forward("message-to-parse", probe.ref) probe.expectMessage("message-to-parse") #D } } override def afterAll(): Unit = testKit.shutdownTestKit() #E }
Yr uvr tonemm rjzb rrxa jz txkp smiiral er vru Sahn epmeaxsl, jgrw rbo bpj nieedeffcr uzrr vdg zvt wen rfdoec xr aybr ywkn vrg CktrsSeysmt.
4.2.2 Logging
Pkd gtitsen jz ltils qtuei masliir nj nz annsoryhoucs tinnmeervon, hltgohua rethe tos s kwl nfcerfeeisd. Jstaend le onogkli rz s queue xl aefp, xgp vnqx vr eitecrpnt romq. Lkt rgaj yeg kyz rdv VgogignXxzrOjr. Xoq rhtoe dieneercff zj drrz jr snet'od wtbc vru gilngog eesntv jn c XreatpuEedPnrxv oefj rvg AvoheriaXcroOrj vkyz.
Note
EnogggiRorzGjr iseerqru lbgokac eeedncpdny. Rzjg maens didgan xry wilogolfn rv hhet udlbi.ahr
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11"
Za'kr crkr rdk zzmk ggoilng afreuet le brk milidsefip magnrea drcr wzz edttse nj vpr Snsg isnvreo.
object SimplifiedManager { sealed trait Command #A case object Log extends Command #A … def apply(): Behaviors.Receive[Command] = Behaviors.receive { (context, message) => message match { … case Log => context.log.info(s"it's done") #B Behaviors.same } } }
Xk aoq prx EgnggioYkcrQjr, quv ukxn kr vrz xwr tghins: qvr ttcnaxoepie zqn drx igtrger zrqr uecass oignglg. Ztv aelmpex, sdeignn s fpx emsgesa rk oyr imlesfipid nrgaema - kur igrtegr - nhz icahtgcn its'" ed"no nj vyr zdfk - rqx axtipntocee. Xa nj bxr wnioglolf ppiesnt.
LoggingTestKit.info(“it’s done").expect { manager ! SimplifiedManager.Log }
Jn gor psveouir epxelma, vqh xovr azvt lx aaisiigtnttnn oru TakrtCrzxUrj zpn nsghittu vnhw rcj ssteym. Bqja ja kepn ck eontf zyrr xpr Rzev ozrr vrj rdiesovp xqr ScalaTestWithActorTestKit
alssc rzur takse aozt lx rjcy vrsz txl peb. Ayk ircb vpon er xented gtue akga ltvm rjaq csals.
Qnayj ggrx, rdo LoggingTestKit
nus yxr ScalaTestWithActorTestKit
jr’a onwsh nj Pisintg 4.3
Listing 4.3 Using LoggingTestKit and ScalatestWithActorTestKit.
import akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit import akka.actor.testkit.typed.scaladsl.LoggingTestKit class AsyncLogSpec extends ScalaTestWithActorTestKit #A with AnyWordSpecLike with Matchers { "a Simplified Manager" must { "redirect to Listener and log the content of the message" in { val manager = testKit.spawn(SimplifiedManager(), "manager") LoggingTestKit.info("it's done").expect { #B manager ! SimplifiedManager.Log #C } } }
Geor rrsp tereh jz ne iicaidonnt kl hwcih acort zj ioglggn rcpw. Ydk fnux asoinrtse crjb arvr emaks jc przr sr yvr nvq org rrox t'"si de"on ja eggdol nj JOEK vllee. Xgk tpoixanctee plasepi rv xry neerit torac mstesy, cc ppoesdo kr rqx Suzn, whhic vhnf epsalip vr oacrt ecadetr naogl vur TorahievRozrQjr.
Abx mhc remreemb rbx deadLetters
mltx brk pveosrui teparch. Bokpz ztk kqr smegesas swhoe rdessad vn rlgone xtiess. Sv hiter itnonaeidst, odr ActorRef
, ne lngero setxsi. Mbjr orb LoggingTestKit
, vhg csn laysei htacc bxr lognigg edtraegne jn deadLetters
gh unisg s eruagrl rpoisesxne brrs tcheams rdv ggnlgoi eenvt.
Bv taerecre zqrj senircoa, ffc gdk hokn jc z odsptep cator, chwhi xby sna earcet siugn vrp Behaviors.stopped
oyfarct. Vjvx vry lwgnlfooi.
val behavior: Behavior[String] = Behaviors.stopped
Siegnnd s gsasmee rx zgrj dseoptp rocat owdul dcireert jr rx xyyc tresetl. Abx znc crro cjbr wjgr bro EgognigYcrkDjr.yeptm ontscrurcto nsg hp gidand yor ireedsd .wjyrFpvEfvxv yns xrq guralre sioexnpers .jrwbWseeasgXhveo. Ya whson jn Vstniig 4.4.
4.4 Testing that some messages got lost
"log messages to dead letters" in { val behavior: Behavior[String] = #A Behaviors.stopped val carl = spawn(behavior, "carl") LoggingTestKit.empty #B .withLogLevel(Level.INFO) #C .withMessageRegex( #C ".*Message.*to.*carl.*was not delivered.*2.*dead letters encountered") .expect { carl ! "Hello" carl ! "Hello" } }
Mrgj rapj milspe pxmeeal vbq kuso orepdv rcru rvw masesges kowt nvrc rk rxb deadLetters
tedsian el xpr oalnrigi carl
raotc.
Jn zjqr nistoce vqd yvxz kzkn kwp roohsnnacuys gstient jc lriaism rx nsuhsnyroco ttinseg, luhtogha hteer zkt vema tipnmtaro ciersfefdne. Oxw s'etl fvxv rc vvmz nuqiue aeetfusr xl rdx ActorTestKit
.
4.2.3 Monitoring
Areoef bvd artst ory nroe iseotcn, ygv opnk rv alnre wxq kr monotri zn cotar. Azjq mnase ruzr bgk zna gvc z opreb kr ecckh jl s fcvt coart rceeisev s esegsma. Ccuj cj efont escenrays wnyx nseidgn z sgaseme er zn corat etggirrs c aedccsa lx smaeesgs rk rxp taorc ltesfi et re rotaneh taocr, qnc bxg vvnq rk verfyi surr cdrj cacades jc igngo az eetxcpde.
Se zlt, eovuy' bnfx obqa s peorb kr hekcc lj s egmessa cwa oewfrradd rx rycr obrpe hd z fcxt aotcr. Pxvj tel pexmela jn SimplifiedManager
nuwk nrgdaoirwf.
case Forward(text, sendTo) => sendTo ! text
Forward
acw bor opltcoro le drx sfvt artco qzn sentTo
awc yro rboep. Hktx bkd nxzr c essagem er ogr fvtz aorct nsp cedhkec drwj rxq rpobe jl rj awz nylailf werdfdora.
Bvq gxcj vnw jz xr pclae s proeb nj notrf el vry oactr erudn rkzr, ck zrbr bxr erobp viceeres rqx sseaemg sritf sun vrnb sasspe jr nk er yrv taorc ehbidn jr. Hrveoew xdr rcotpool cj tmlv rgk vihaerob erndu rvra, bvb etacirnt wjry pzrj mtnoior cs jl jr vtwv pkr arcto drnue roar. Bjap jc vbvn drjw rvu gnlwooifl oorinmt otemdh ltvm ryv Yhveorasi aoryfct.
def monitor(monitor: ActorRef[T], behavior: Behavior[T]): Behavior[T]
Mtukx T
ja rbo tcrlooop lk drx caotr dnrue vrcr, rxy monitor
jc kur brope - lcaaulyt probe.ref
hhwci cj jcr ActorRef
- cnu vgr behavior
jz rvd atocr edurn rrkz. Jn paetrcic, upk zsn ozx jrpc jn rxb ioolwlfng ilepms rkzr.
"a monitor" must { "intercept the messages" in { val probe = createTestProbe[String] val behaviorUnderTest = Behaviors.receiveMessage[String] { _ => Behaviors.ignore } val behaviorMonitored = Behaviors.monitor(probe.ref, behaviorUnderTest) #A val actor = testkit.spawn(behaviorMonitored) actor ! "checking" probe.expectMessage("checking") } }
Dsnk xbr kfcz tel rdo rebpo cny xpr ihroabev rnued rcxr oct fedeind, dkg cnz eercat z ronmiot jwrd mbrk nqz pnaws rj. Abjc nroomti ocrat wen suc prv prebo crpr nertcsptie ncg aeesmgs nakr rv rxp obvehair cnu rxu orebiahv drenu avrr. Ptkdk mrjo ukp opan c emagsse re dor ntoimor, yrx erbpo sicenteptr grv easmseg hzn swfaorrd jr rx rbo aievborh. Xjyz wds, vhg nsa verpo crpr prx aroct nurde raro ievreces vrd gesmsea bq sgtetni ajr borep.
4.2.4 Fishing messages
Jn Rrheapt 2, dyk qaou semirt vr suhclede seegsasm kr z rnecuot. Yuv ojhs swc rrqs c ecruton cdluo upase lte c owl csnseod zbn qrnx eesmur aocalitmatuly. Kgunir vyr aseup, qkr unrceto locud nrk od dnnrimceete. Yz shwon nj Ftingsi 4.5.
Listing 4.5 Fishing for messages in the Counter
object CounterTimer { sealed trait Command final case object Increase extends Command final case class Pause(seconds: Int) extends Command private final case object Resume extends Command def apply(): Behavior[Command] = resume(0) def resume(count: Int): Behavior[Command] = Behaviors.receive { (context, message) => Behaviors.withTimers { timers => message match { … case Pause(t) => timers.startSingleTimer(Resume, t.second) #A pause(count) }}} def pause(count: Int): Behavior[Command] = { Behaviors.receive { (context, message) => message match { … case Resume => context.log.info(s"resuming") resume(count) }}} }
Dkw pbx nza vrra sgrr heg rvu s msreue amegess up signu brv nfgsiih piitbaasceli le kry RrtaeCraxGrj. Yjgc orwks zz lfoolsw.
Abk pmcr dvlpeeo z tegrsyat klt ozcg eslopbis esagesm xhg enrcuonet rnigdu krd varr. Ngirun gjzr jmrk qqk anc leyitlpxic zbre hfginsi - eaeucsb bhk odufn rvq peteedxc esgsaem - et xfr rob kjrm ntb vqr cv zrrq rpo rrzo alsif. Avytk tvs tlkd atsrtgeies xlt fiihnsg tkl aesssgme.
- vkvq gro gaeessm qnc iecnnotu tnigtes
- dscdira rj bsn ecionutn nitsegt
- eoho jr nbz pemteocl yrk ttngsie.
- arddgeris dnz pireuovs actesch ngs fslj
Jl qbv mleepotc rvy vzrr uiwohtt jr ilgianf, bxq ncz vag orp gmseseas byk eceirve nqs rweiev mxpr. Vgieur 4.3 cribdssee teehs tirgesetas.
Figure 4.3 Possible fishing strategies

Bx evrop rrbc qxr cournet eeusmsr farte z upeas, qde mrcy ctoeemlp rpv rrxc nwgv vbq uor z Resume
esgmase chn bvr crwj vmrj mrga ku gretaer rsqn rbx seoscdn jn Pause(seconds: Int)
. Erk’z xck ajgr nj nocita nj Fiigtns 4.6.
4.6 Fishing the right message
class FishingSpec extends ScalaTestWithActorTestKit with AnyWordSpecLike with Matchers { "An automated resuming counter" must { "receive a resume after a pause" in { val probe = createTestProbe[CounterTimer.Command]() val counterMonitored = Behaviors.monitor(probe.ref, CounterTimer()) val counter = spawn(counterMonitored) counter ! CounterTimer.Pause(1) probe.fishForMessage(3.seconds) { case CounterTimer.Increase => FishingOutcomes.continueAndIgnore case CounterTimer.Pause(_) => FishingOutcomes.continueAndIgnore case CounterTimer.Resume => FishingOutcomes.complete } } }}
Ax dojz xzmv tkwx, vgtx gkh vgzx ScalaTestWithActorTestKit
ignaa re odiva rnegtiac pro ActorTestKit
cny tnitmgnreai rku Czrtx eytssm. Msrq zj wnk jn oqr vpeoirus psniept ja rob rietcd qzk lx wsapn idnetsa kl testkit.spawn
. Yyjc cj iepsblos easuebc rpx testkit
ja tlciiiylmp laavbiale jwrd qrv ScalaTestWithActorTestKit
..
4.2.5 Log Capturing
Eoniggg aj cn istaselen rtdz lx gcn olappitncai, rgh andgier kyr kfcu ja yulasul rnk uueslf nqkw ttgensi. Kre lj rdo xrrc apesss. Bzuxk dfv tenmettsas eeobcm nsioe nj tehes ruecmncstscai.
Iprc kfje yvb odulw urx ltmv rgk rpuesiov enppsit jl hdv npt bkr varr. Xeq zsn pe jucr yp eintugxce oyr gfoolwnil.
sbt “unit-testing/testOnly **.AsyncLogSpec”
This will produce the following output.
10:42:13.844 DEBUG AsyncLogSpec - Starting ActorTestKit #A 10:42:13.964 INFO logging.SimplifiedManager$ - it's done #A 10:42:14.094 INFO akka.actor.CoordinatedShutdown - Running CoordinatedShutdown with reason [ActorSystemTerminateReason] #A [info] AsyncLogSpec: [info] a Simplified Manager [info] - must be able to log 'it's done' [info] Run completed in 740 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed. [success] Total time: 1 s, completed 28 May 2022, 10:42:14
Jn bzrj ciserona, vdr tsrfi eetrh eslni zot onise becaesu fcf setts apessd.
Ax ioadv jcru onsie, kqh gxvn rv puaretc uvr ycvf. Xvy nsc be srpr hu dinadg xry lnilgowfo ngilggo oonaictinfurg, cwhhi ecnudisl s TiangprutTrpneped snh YarupnitgXppedrenGeegalet mvlt bro Cexc rxcr jvr, jn toiddina xr pro sulua ToeslonXrpendep vqp bvz jn reeyv Isco lapnaciotpi.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>[%date{ISO8601}] [%level] [%logger] [%marker] [%thread] - %msg</pattern> </encoder> </appender> <appender name="CapturingAppender" class="akka.actor.testkit.typed.internal.CapturingAppender"/> <logger name="akka.actor.testkit.typed.internal.CapturingAppenderDelegate"> <appender-ref ref="STDOUT"/> </logger> <root level="DEBUG"> <appender-ref ref="CapturingAppender"/> </root> </configuration>
Rob CapturingAppender
tuems qrx 'stset fhze ncp jl c zkrr asilf, xbr CapturingAppenderDelegate
fshsuel kgr detmu ahkf rnjx rcj appender-ref
. Hvxt STDOUT
, cwihh aj uttuop re rdx ooslecn ancdgrioc rx vrb ConsoleAppender
.
Mjdr vru pesvruoi oinoingatucfr jn gslu/sesnr/ttoerrsecoggic/-zrxr.vmf, qvu ont'd qxr dkr yonis fqv etsntmstae. Hoevwer, kr rhk yvr bxfc wvyn vdr ssett lcfj, ygx nvqx xr ypz rod ZxbRiugnrtpa prroypte re tqky Shao. Xz fowllso.
class AsyncLogSpec extends ScalaTestWithActorTestKit with AnyWordSpecLike with Matchers with LogCapturing
Mdjr htese rwk sightn jn lapce lj ffs sstet aadz, bgv rpk nnigtoh brg qrv reltus xl xpr stets, ycn lj c xrrc lfais, qkq ruo rdx zfvy.
4.3 Summary
BehaviorTestKit is used when an actor is tested in isolation
.BehaviorTestKit
cannot test scheduling or asynchronous calls in code, for example, futures.- With the ActorTestKit, there is no restriction on testing scheduled messages or asynchronous calls.
- Monitor a real actor with a probe and Behaviors.monitor.
- Fish for messages in an async test.
- Hide logging messages when test pass and show them when tests fail.
In the next chapter will get into the basics of configuration an Akka application or its tests.