Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test that Actor Foo sends messages to newly created child actors Bar?

Tags:

java

akka

I have an actor FooActor that gets passed the Props to instantiate several BarActors and send BarMessages to it. The code works, but I am having a hard time writing tests for it. The added restriction is that I can only use Java code in this app, no Scala code.

After several attempts, this seems to be my best effort so far:

@Test
public void testJavaTestKit() {

    new JavaTestKit(system) {{
        JavaTestKit probe = new JavaTestKit(system);

        // pretending that the probe is the receiving Bar, by returning it in the Props
        Props barActorProps = Props.create(BarActor.class, new Creator() {
            @Override
            public Object create() {
                return probe.getRef();
            }
        });
        Props props = Props.create(FooActor.class, barActorProps);
        ActorRef subject = system.actorOf(props);

        Object msg = // basically irrelevant, will trigger Bar instantiation and message sending

        subject.tell(msg, probe.getRef());

        expectMsgClass(Bar.BarMessage.class);
        expectNoMsg();
    }};
}

It all seems to make sense to me, but even though I can see messages being sent to newly created Bar instances, the first assertion fails. What am I doing wrong?

Update:

The thing that makes this different from the Akka documentation example, is that I don't want to pass an existing actor that receives the message. Instead, I want to pass the Props that is used to create instances of child actors instead. In the test, I want my probe to receive messages to those newly created actors. This is why I added the Props.create construct that should return the same probe actor all the time. Just now I saw this comment in the Creator.create API:

This method must return a different instance upon every call.

So this will obviously not work, as that it precisely what I want. So my general question remains: how can I test for messages being sent to newly created child actors?

like image 792
Jeroen Kransen Avatar asked Sep 24 '14 12:09

Jeroen Kransen


2 Answers

You're trying to "cheat" on how the child actors are initialized (by passing probeRef) to have flexibility in testing them, but the problem is that the even Creator is generic, used in standard context getContext().actorOf(), cannot return ActorRef as a result of the create method. The contract needs always be as follow Creator<T extends Actor>.

Please have a look at ActorCreationTest

I might be wrong because, I don't know how your FooActor is implemented but if you will use stadard pattern getContext().actorOf() default Akka akka.actor.CreatorConsumer will not accept your Consumer.

caused by: java.lang.ClassCastException: akka.actor.RepointableActorRef cannot be cast to akka.actor.Actor
    at akka.actor.CreatorConsumer.produce(Props.scala:335)
    at akka.actor.Props.newActor(Props.scala:252)
    at akka.actor.ActorCell.newActor(ActorCell.scala:552)
    at akka.actor.ActorCell.create(ActorCell.scala:578)
    ... 9 more

Instead of returning ActorRef to probe you may try return anonymous forwarder Actor ?

import akka.actor.*;
import akka.japi.Creator;
import akka.testkit.JavaTestKit;
import org.junit.Test;

public class ActorTest {

    @Test
    public void testJavaTestKit() {
        ActorSystem system = ActorSystem.create("Acceptor");

        new JavaTestKit(system) {{
            JavaTestKit probe = new JavaTestKit(system);

            Creator creator = () -> new UntypedActor() {
              @Override
              public void onReceive(Object message) throws Exception {
                  probe.getRef().tell(message, sender());
              }
            };


            ActorRef subject = system.actorOf(Props.create(FooActor.class, creator));

            subject.tell(new Bar().new BarMessage(), probe.getRef());
            probe.expectMsgClass(Bar.BarMessage.class);


        }};
    }

}


class BarActor extends UntypedActor {

    @Override
    public void onReceive(Object message) throws Exception {

    }
}

class FooActor extends UntypedActor {

    ActorRef barRef;

    public static Props props(final Creator creator) {
        return Props.create(FooActor.class, () -> new FooActor(creator));
    }
    public FooActor(Creator creator) {
        barRef = getContext().actorOf(Props.create(creator),"bar");
    }

    @Override
    public void onReceive(Object message) throws Exception {
        barRef.tell(message,sender());
    }
}

class Bar {
    class BarMessage {

    }
}
like image 125
hicolour Avatar answered Oct 04 '22 02:10

hicolour


I think something like this should work for your case.

class ActorSpec extends Specification {
    @Shared
    private ActorSystem system
    @Shared
    private ActorRef actorRef
   def setupSpec() {
        system = ActorSystem.create("test-system",ConfigFactory.load("application.conf"))
    }
   def "Verify message received"() {
     given:
            JavaTestKit probe = new JavaTestKit(system)
            final Props props = Props.create(BarActor.class)
            actorRef = system.actorOf(props)
        when:
            actorRef.tell(new BarMessage(), probe.getRef())
        then:
            probe.expectMsgClass(Bar.BarMessage.class);
    }
 def cleanupSpec() {
        JavaTestKit.shutdownActorSystem(system)
        system = null
    }
like image 35
Shweta Avatar answered Oct 04 '22 01:10

Shweta