2015-10-18 81 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 
    } 

} 

如果我想要寫TopActor測試,以檢查發送到AnotherActor的消息是「你好」,我怎麼替換實施AnotherActor?它看起來像TopActor直接創建這個孩子,所以這是不容易訪問。

回答

8

以下方法似乎有效,但直接覆蓋anotherActor的val似乎有點粗糙。我想知道是否有任何其他清潔劑/推薦的解決方案,這就是爲什麼我還在問這個問題,即使我有這個工作答案:

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

至於有沒有其他的答案,這個解決方案,我有一個給予好評,我想我會接受我自己的回答:) –

+1

您的'testTopActor.underlyingActor'將同時擁有'anotherActor'和'TopActor.anotherActor'。如果你的'anotherActor'在其構造函數或任何生命週期函數中沒有做任何事情,但是如果有某事正在運行網絡/數據庫連接(我知道這很糟糕,但僅僅是爲了說明這一點),那麼當你創建'testTopActor'時,你將有2個這樣的操作運行。也許這是值得注意的。 – CrazyGreenHand

0

你可能要檢查這個解決方案,我在網上找到(學分去斯蒂格Brautaset): http://www.superloopy.io/articles/2013/injecting-akka-testprobe.html

這是一個完美的解決方案,但有點複雜。它通過特性(ChildrenProvider)創建anotherActor開始,比您可以使用返回AnotherActor實例的productionChildrenProvider。在測試中,testChildrenProvider將返回一個TestProbe。看看測試代碼,它非常乾淨。但演員實施是我必須考慮的事情。

0

我對斯卡拉自己很新。儘管如此,我面臨同樣的問題,並採取以下措施。我的方法背後的想法是注入如何產生一個孩子演員到相應的父母的信息。爲了確保乾淨初始化我創建了一個工廠方法,我用它來實例化的演員本身:

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 
} 

然後,你可以這樣測試:

// 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") 
} 

這樣做,你提取的產卵行爲將父母放入一個匿名函數中,這個函數可以在測試中由FakeChild演員代替,這個演員完全在你手中。從FakeChild向TestProbe發送信息可以解決您的測試問題。

我希望有幫助。

1

也許這個解決方案將幫助任何人解決這個問題。

我有一個父 - 演員類創建一些兒童演員。 Parent-actor像轉發器一樣行爲,它通過提供的id來檢查child是否存在,如果是,則發送消息給它。在父母 - 演員中,我使用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 
    } 
    }