Search code examples
unit-testingjava-8akkaakka-testkit

Akka and Java 8 TestKit by Example


Java 8 and Akka 2.12:2.5.16 here. I am trying to write my first (ever) Akka unit test leveraging Akka TestKit and am struggling to apply the principles I see in the (very few) examples I've been able to find online.

My actors:

public class Child extends AbstractActor {
    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .match(Init.class, init -> {
                int workUnit = workService.doSomeWork();
                log.info("Performed work on {}", workUnit);
            }).build();
    }
}

public class Master extends AbstractActor {
    @Inject @Named("CHILD")
    private ActorRef child;

    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .match(Init.class, init -> {
                child.tell(init, self());
            }).build();
    }
}

Very, very simple. So now I just want to write a unit tests that verifies that when the Master actor receives an Init message, that it forwards that message onto its Child actor. My best attempt thus far:

@RunWith(MockitoJUnitRunner.class)
public class MasterTest {
    private ActorSystem actorSystem;

    @Before
    public void setup() {
        actorSystem = ActorSystem.create("test-system");
    }

    @After
    public void teardown() {
        Duration duration = Duration.create(10L, TimeUnit.SECONDS);
        TestKit.shutdownActorSystem(actorSystem, duration, true);
        actorSystem = null;
    }

    @Test
    public void onInit_shouldSendFordwardToChild() {
        // Given
        TestKit testKit = new TestKit(actorSystem);
        ActorRef master = actorSystem.actorOf(Props.create(Master.class, testKit));

        // When
        master.tell(new Init(), ActorRef.noSender());

        // Then
        testKit.expectMsg(Init.class);
    }
}

When I run this I get:

java.lang.IllegalArgumentException: no matching constructor found on class com.me.myapp.Master for arguments [class akka.testkit.TestKit]

Can someone please help me wire the TestKit instance into my Master actor, and also help me figure out how to refactor MasterTest such that it verifies what I'm trying to accomplish? Thanks in advance!


Solution

  • I figured it out, can't believe how difficult it was to get this to work :-/

    In application.conf:

    MyAkkApp {
      akka {
        remote {
          enabled-transports = ["akka.remote.netty.tcp"]
          netty.tcp {
            hostname = "127.0.0.1"
            port = 2553
          }
        }
      }
    }
    

    Then:

    @RunWith(MockitoJUnitRunner.class)
    public class MasterTest extends TestKit {
        static ActorSystem actorSystem = ActorSystem.create("MyAkkaApp",
            ConfigFactory.load().getConfig("MyAkkaApp"));
    
        static TestProbe child;  // The mock child
        static ActorRef master;
    
        @BeforeClass
        public static void setup() {
            child = new TestProbe(actorSystem, "Child");
            master = actorSystem.actorOf(Props.create(new Creator<Actor>() {
                @Override
                public Actor create() throws Exception {
                    return new Master(child.ref());
                }
            }));
        }
    
        public MasterTest() {
            super(actorSystem);
        }
    
        @Test
        public void onInit_shouldSendFordwardToChild() {
            // Given
            Init init = new Init();
    
            // When
            master.tell(init, super.testActor());
    
            // Then
            child.expectMsg(init);  // Child should have received it
            expectNoMessage();  // Master should not be returning to sender
        }
    }
    

    Come on Akka folks! Support yields adoption, adoption leads to standardization, standardization means you get to sell 6-figure corporate support licenses.