2015-10-18 3 views
10

私は親アクターをAkkaに持っていますが、それは初期化時に子アクターを直接作成します。親アクターの単体テストを書きたいとき、どのようにして子アクターをTestProbeやモックに置き換えることができますか?以下の不自然なコードサンプルと例えばAkkaシステムをテストするために子供の俳優を模擬する方法は?

、:

class TopActor extends Actor { 
    val anotherActor = context.actorOf(AnotherActor.props, "anotherActor") 

    override def receive: Receive = { 
    case "call another actor" => anotherActor ! "hello" 
    } 
} 

class AnotherActor extends Actor { 

    override def recieve: Receive = { 
    case "hello" => // do some stuff 
    } 

} 

私はAnotherActorに送信されたメッセージを確認するために、TopActorのためのテストを書きたい場合は、「こんにちは」で、どのように私はの実装を置き換えますかAnotherActor? TopActorがこの子を直接作成するように見えるので、これはアクセスが容易ではありません。

答えて

8

次のアプローチはうまくいくようですが、別のアクターの値をオーバーライドするのは少し原油のようです。

class TopActorSpec extends MyActorTestSuiteTrait { 
    it should "say hello to AnotherActor when receive 'call another actor'" { 
    val testProbe = TestProbe() 

    val testTopActor = TestActorRef(Props(new TopActor { 
     override val anotherActor = testProbe.ref 
    })) 

    testTopActor ! "call another actor" 
    testProbe.expectMsg(500 millis, "hello") 
    } 
} 
+0

が、このソリューションには他の答えはないと私はupvoteを得たように、私は私がすると思います:私は親-俳優がどのように動作するかをテストしたいと彼がそれに送信することは、子供の場合、私はコードの下に使用します私自身の答えを受け入れる:) –

+1

あなたの 'testTopActor.underlyingActor'は' anotherActor'と 'TopActor.anotherActor'の両方を持っています。 'anotherActor'がコンストラクタやライフサイクル関数で何もしていない場合でも、何かが実行中であれば問題ありません。ネットワーク/データベース接続をコンストラクタで(私はそれが悪いことを知っているが、ポイントを説明するためだけです)、あなたは 'testTopActor'を作成するときに2つのそのような操作を実行させます。おそらく、そのようなことに注意するのは良いことです。 – CrazyGreenHand

0

あなたは、私がオンラインで見つけるこのソリューションをチェックすることもできますクレジットに行く(:私はまだ質問を、なぜ私はこの作業答えを持っているにもかかわらずされている他のクリーナー/推奨ソリューションは、あった場合、私は思っていましたStig Brautaset): http://www.superloopy.io/articles/2013/injecting-akka-testprobe.html

これは洗練されたソリューションですが、少し複雑です。 AnotherActorインスタンスを返すproductionChildrenProviderを持つことができるよりも、特性(ChildrenProvider)を介してanotherActorを作成することから始めます。テスト中に、testChildrenProviderは代わりにTestProbeを返します。 テストコードを見ると、きれいです。しかし、俳優の実装は私が考えなければならないものです。

0

私はScala自身にかなり新しいです。それにもかかわらず、私は同じ問題に直面し、次のようにそれに近づいた。私のアプローチの背後にあるアイデアは、子アクターを対応する親にスポーンする方法についての情報を注入することです。私は俳優自身をインスタンス化するために使用するファクトリメソッド作成クリーンな初期化を確認するために:あなたはから産卵行動を抽出し、これを行うことにより

// This returns a new actor spawning function regarding the FakeChild 
object FakeChildSpawner{ 
    def spawn(probe :ActorRef) = { 
    (context: ActorContext) => { 
     context.actorOf(Props(new FakeChild(probe))) 
    } 
    } 
} 

// Fake Child forewarding messages to TestProbe 
class FakeChild(probeRef :ActorRef) extends Actor { 
    def receive = { 
    case msg => probeRef ! (msg) 
    } 
} 

"trigger actions of it's children" in { 
    val probe = TestProbe() 

    // Replace logic to spawn Child by logic to spawn FakeChild 
    val actorRef = TestActorRef(
    new Parent(FakeChildSpawner.spawn(probe.ref)) 
) 

    val expectedForewardedMessage = "expected message to child" 
    actorRef ! "message to parent" 

    probe.expectMsg("expected message to child") 
} 

object Parent { 
    def props() :Props { 
    val childSpawner = { 
     (context :ActorContext) => context.actorOf(Child.props()) 
    } 
    Props(classOf[Parent], spawnChild) 
    } 
} 

class Parent(childSpawner: (ActorContext) => ActorRef) extends Actor { 
    val childActor = childSpawner(context) 
    context.watch(childActor) 

    def receive = { 
    // Whatever 
    } 
} 

object Child { 
    def props() = { Props(classOf[Child]) } 
} 

class Child extends Actor { 
    // Definition of Child 
} 

を次に、あなたがこのようにそれをテストすることができます親は、テストの中であなたの手に完全にあるFakeChildアクターに置き換えられる無名関数に変換されます。 FakeChildからTestProbeへのフォアディングメッセージは、テストの問題を解決します。

私は役立つことを願っています。

1

おそらく、この解決策は誰でもこの問題を解決するのに役立ちます。

私はいくつかの子アクターを作成する親アクタークラスを持っています。親アクターはフォワーダのように動作し、指定されたIDで子が存在するかどうかをチェックし、そうであればメッセージを送信します。親アクターでは、私はcontext.child(actorId)を使って子が既に存在するかどうかを確認します。

"ParentActor " should " send XXX message to child actor if he receives YYY message" in { 
    val parentActor = createParentActor(testActor, "child_id") 
    parentActor ! YYY("test_id") 
    expectMsg(XXX) 
} 

def createParentActor(mockedChild: ActorRef, mockedChildId: String): ParentActor = { 
    TestActorRef(new ParentActor(){ 
     override def preStart(): Unit = { 
     context.actorOf(Props(new Forwarder(mockedChild)), mockedChildId) 
     } 
    }) 
    } 

    class Forwarder(target: ActorRef) extends Actor { 
    def receive = { 
     case msg => target forward msg 
    } 
    } 
関連する問題