4

Java 8 and Akka (Java API) 2.12:2.5.16 here. I have the following message:

public class SomeMessage {
    private int anotherNum;

    public SomeMessage(int anotherNum) {
        this.anotherNum = anotherNum;
    }

    public int getAnotherNum() {
        return anotherNum;
    }

    public void setAnotherNum(int anotherNum) {
        this.anotherNum = anotherNum;
    }
}

And the following actor:

public class TestActor extends AbstractActor {
    private Integer number;

    public TestActor(Integer number) {
        this.number = number;
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .matchAny(message -> {
                if (message instanceof SomeMessage) {
                    SomeMessage someMessage = (SomeMessage) message;
                    System.out.println("someMessage contains = " + someMessage.getAnotherNum());
                    someMessage.setAnotherNum(number);
                }
            }).build();
    }
}

And the following unit test:

@RunWith(MockitoJUnitRunner.class)
public class TestActorTest {
    static ActorSystem actorSystem;

    @BeforeClass
    public static void setup() {
        actorSystem = ActorSystem.create();
    }

    @AfterClass
    public static void teardown() {
        TestKit.shutdownActorSystem(actorSystem, Duration.create("10 seconds"), true);
        actorSystem = null;
    }

    @Test
    public void should_alter_some_message() {
        // given
        ActorRef testActor = actorSystem.actorOf(Props.create(TestActor.class, 10), "test.actor");
        SomeMessage someMessage = new SomeMessage(5);

        // when
        testActor.tell(someMessage, ActorRef.noSender());

        // then
        assertEquals(10, someMessage.getAnotherNum());
    }
}

So all I'm trying to verify is that TestActor does in fact receive a SomeMessage and that it alters its internal state.

When I run this unit test, it fails and is as if the actor never receives the message:

java.lang.AssertionError: 
Expected :10
Actual   :5
 <Click to see difference>

    at org.junit.Assert.fail(Assert.java:88)
    at org.junit.Assert.failNotEquals(Assert.java:834)
    at org.junit.Assert.assertEquals(Assert.java:645)
  <rest of trace omitted for brevity>

[INFO] [01/30/2019 12:50:26.780] [default-akka.actor.default-dispatcher-2] [akka://default/user/test.actor] Message [myapp.actors.core.SomeMessage] without sender to Actor[akka://default/user/test.actor#2008219661] was not delivered. [1] dead letters encountered. If this is not an expected behavior, then [Actor[akka://default/user/test.actor#2008219661]] may have terminated unexpectedly, This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.

But when I modify the test method and introduce a Thread.sleep(5000) into it (after the tell(...)) it passes with flying colors:

@Test
public void should_alter_some_message() throws InterruptedException {
    // given
    ActorRef testActor = actorSystem.actorOf(Props.create(TestActor.class, null, 10), "test.actor");
    SomeMessage someMessage = new SomeMessage(5);

    // when
    testActor.tell(someMessage, ActorRef.noSender());

    Thread.sleep(5000);

    // then
    assertEquals(10, someMessage.getAnotherNum());
}

What's going on here?! Obviously I don't want my actor tests littered with sleeps, so what am I doing wrong here and what is the fix? Thanks in advance!

hotmeatballsoup
  • 385
  • 6
  • 58
  • 136

2 Answers2

4

@Asier Aranbarri is correct by saying that you don't let you actor to finish its work.

Actors have asynchronous nature and though they don't implement Runnable they are executed separately from the thread that is used to send a message from.

You send a message to an actor and then immediately assert that the message was changed. As actor runs in asynchronous context, ie different thread, it still has not processed the incoming message. Thus putting Threed.sleep allows actor process the message and only after this the assertion is done.

I may suggest some changes to your initial design that would marry well with akka nature.

First of all, akka does not suggest using messages with mutability. They must be immutable. In you case this is broken with method SomeMessage#setAnotherNum. Remove it:

public class SomeMessage {
    private int anotherNum;

    public SomeMessage(int anotherNum) {
        this.anotherNum = anotherNum;
    }

    public int getAnotherNum() {
        return anotherNum;
    }
}

After this create a new instance of SomeMessage instead of changing your incoming message in TestActor and send it back to context.sender(). Like defined here

static public class TestActor extends AbstractActor {
    private Integer number;

    public TestActor(Integer number) {
        this.number = number;
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .matchAny(message -> {
                    if (message instanceof SomeMessage) {
                        SomeMessage someMessage = (SomeMessage) message;
                        System.out.println("someMessage contains = " + someMessage.getAnotherNum());
                        context().sender().tell(new SomeMessage(number + someMessage.getAnotherNum()), context().self());
                    }
                }).build();
    }
}

Now, instead of changing inner state of a message, a new message is created with new state and the later message is returned back to sender(). This is the appropriate usage of akka.

This allows test to use a TestProbe and be redefined as follows

@Test
public void should_alter_some_message() {
    // given
    ActorRef testActor = actorSystem.actorOf(Props.create(TestActor.class,10));
    TestJavaActor.SomeMessage someMessage = new SomeMessage(5);
    TestProbe testProbe = TestProbe.apply(actorSystem);

    // when
    testActor.tell(someMessage, testProbe.ref());

    // then
    testProbe.expectMsg(new SomeMessage(15));
}

TestProbe emulates a sender and captures all incoming message/replies from TestActor. Note that expectMsg(new SomeMessage(15)) is used instead of an assert. It has an inner blocking mechanism that waits for message to be received before the assertion is done. This is what happens in testing actors example.

To make expectMsg assert correctly, you must override equals method in your class SomeMessage

Edit:

Why does Akka frown upon changing SomeMessage's internal state?

One of the powers of akka is that it does not require synchronisation or wait/notify to control access to shared data. But this can be achieved only with message immutability. Imagine you send a mutable message that you change at the exact time actor processes it. This can cause race conditions. Read this for more details.

And (2) does the same apply to changing Actors' internal state? Is it "OK" for ActorRefs to have properties that can be changed, or does the community frown upon that as well (and if so, why!)?

No, this does not apply here. If any state is incapsulated within an actor and only it can change it, it's completely fine having mutability.

Ivan Stanislavciuc
  • 7,140
  • 15
  • 18
  • Thanks @Ivan (+1), everything you say makes perfect sense! However I just have two quick questions regarding Akka's nature that you mentioned, if you don't mind! **(1)** Why does Akka frown upon changing `SomeMessage`'s internal state? Any chance you could elaborate on a bad situation that is created by changing the message's internal state? And **(2)** does the same apply to changing Actors' internal state? Is it "OK" for `ActorRefs` to have properties that can be changed, or does the community frown upon that as well (and if so, why!)? Thanks again for a wonderful answer here! – hotmeatballsoup Jan 30 '19 at 21:21
  • great explanation, I was curious. +1 – aran Feb 14 '19 at 13:00
2

I think you don't let the actor do his job. Maybe the AkkaActor starts his own thread? I think Actor does implement Runnable, but I'm not really an expert on Akka. --> edit Actor is an interface, glad I said I was not an expert..

My guess is that by sleeping your main thread, you give time to the Actor's "thread" to finish his method.

I know this may not be helpful, but was way too long to put it in comment. : (

aran
  • 10,978
  • 5
  • 39
  • 69
  • Thanks @Asier (+1) I appreciate the help but as you can see I'm following the same basic template that the [Akka TestKit docs themselves](https://developer.lightbend.com/guides/akka-quickstart-java/testing-actors.html) prescribe. No `Thread.sleeps` in those docs! I agree with you in principle but from what I can tell I'm using the Akka TestKit API correctly and they certainly wouldn't expect their users to put an ugly `Thread.sleep` inside each and every unit test! Something else is off here... – hotmeatballsoup Jan 30 '19 at 18:33
  • @hotmeatballsoup, just found this: https://stackoverflow.com/questions/29270024/how-do-i-test-an-akka-actor-that-sends-a-message-to-another-actor , maybe it can be helpful – aran Jan 30 '19 at 19:25
  • 1
    Thanks again, but that other question uses Scala, and the Akka Java vs Akka Scala APIs are significantly different from one another. I'm also not seeing anything in their that jumps out at me as being the "*aha!*" culprit to my problem, but I do appreciate your help here! – hotmeatballsoup Jan 30 '19 at 19:31