Why does adding Thread.sleep make my Akka TestKit unit tests pass?

hotmeatballsoup :

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!

Ivan Stanislavciuc :

@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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=95464&siteId=1