Akka TestKit provides a comprehensive Java API that offers the same functionality as the Scala API with Java-friendly method signatures, types, and modern Java 8+ features. The Java API is located in the akka.testkit.javadsl package and includes specialized classes designed for Java developers.
class TestKit(system: ActorSystem) {
// Java Duration-based methods
def expectMsg(duration: java.time.Duration, obj: AnyRef): AnyRef
def expectMsgClass(duration: java.time.Duration, clazz: Class[_]): AnyRef
def expectMsgEquals(duration: java.time.Duration, obj: AnyRef): AnyRef
def expectNoMessage(duration: java.time.Duration): Unit
def expectNoMessage(): Unit
// Supplier-based methods for lazy evaluation
def within(duration: java.time.Duration, supplier: Supplier[_]): AnyRef
def awaitCond(duration: java.time.Duration, supplier: Supplier[Boolean]): Unit
def awaitAssert(duration: java.time.Duration, supplier: Supplier[_]): AnyRef
// Message reception
def receiveOne(duration: java.time.Duration): AnyRef
def receiveN(n: Int, duration: java.time.Duration): java.util.List[AnyRef]
// Actor references and system access
def getSystem(): ActorSystem
def getTestActor(): ActorRef
def getLastSender(): ActorRef
// Lifecycle
def watch(actorRef: ActorRef): ActorRef
def unwatch(actorRef: ActorRef): ActorRef
}Modern Java API with java.time.Duration support and Java 8+ functional interfaces.
class EventFilter(clazz: Class[_], system: ActorSystem) {
def message(pattern: String): EventFilter
def source(source: String): EventFilter
def occurrences(count: Int): EventFilter
def from(source: String): EventFilter
def intercept[T](supplier: Supplier[T]): T
def matches(logEvent: LogEvent): Boolean
}
object EventFilter {
def error(system: ActorSystem): EventFilter
def warning(system: ActorSystem): EventFilter
def info(system: ActorSystem): EventFilter
def debug(system: ActorSystem): EventFilter
def forException(clazz: Class[_ <: Throwable], system: ActorSystem): EventFilter
}Fluent Java API for event filtering with method chaining.
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.testkit.javadsl.TestKit;
import java.time.Duration;
import org.junit.Test;
import org.junit.AfterClass;
public class MyActorTest {
static ActorSystem system = ActorSystem.create("TestSystem");
@Test
public void testActorResponse() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(MyActor.class));
// Send message and expect response
actor.tell("ping", getTestActor());
expectMsg(Duration.ofSeconds(1), "pong");
}};
}
@Test
public void testNoMessage() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(QuietActor.class));
actor.tell("ignore-me", getTestActor());
expectNoMessage(Duration.ofMillis(500));
}};
}
@AfterClass
public static void teardown() {
TestKit.shutdownActorSystem(system);
}
}import akka.testkit.javadsl.TestKit;
public class TypedMessageTest {
@Test
public void testTypedMessages() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(StatusActor.class));
// Expect specific message class
actor.tell("get-status", getTestActor());
StatusResponse response = expectMsgClass(
Duration.ofSeconds(1),
StatusResponse.class
);
assertEquals("active", response.getStatus());
assertEquals(42, response.getCount());
}};
}
}import java.util.function.Supplier;
public class TimingTest {
@Test
public void testWithinBlock() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(TimedActor.class));
// Execute within time bounds using Supplier
String result = (String) within(Duration.ofSeconds(3), () -> {
actor.tell("timed-request", getTestActor());
return expectMsg(Duration.ofSeconds(2), "timed-response");
});
assertEquals("timed-response", result);
}};
}
@Test
public void testAwaitCondition() {
new TestKit(system) {{
SharedState state = new SharedState();
ActorRef actor = system.actorOf(Props.create(StateActor.class, state));
actor.tell("initialize", getTestActor());
// Wait for condition with Supplier
awaitCond(Duration.ofSeconds(5), () -> state.isReady());
assertTrue(state.isReady());
}};
}
}public class MessageReceptionTest {
@Test
public void testReceiveMessages() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(BatchActor.class));
// Trigger batch of messages
actor.tell("send-batch", getTestActor());
// Receive single message
Object first = receiveOne(Duration.ofSeconds(1));
assertEquals("message-1", first);
// Receive multiple messages
List<Object> messages = receiveN(3, Duration.ofSeconds(2));
assertEquals(3, messages.size());
assertEquals("message-2", messages.get(0));
assertEquals("message-3", messages.get(1));
assertEquals("message-4", messages.get(2));
}};
}
}public class LifecycleTest {
@Test
public void testActorTermination() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(LifecycleActor.class));
// Watch for termination
watch(actor);
// Trigger termination
actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
// Expect termination message
Terminated terminated = expectMsgClass(
Duration.ofSeconds(1),
Terminated.class
);
assertEquals(actor, terminated.getActor());
}};
}
}import akka.testkit.javadsl.EventFilter;
public class EventFilterTest {
@Test
public void testErrorFiltering() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(LoggingActor.class));
// Filter expected error messages
EventFilter.error(system)
.message("Something went wrong")
.occurrences(1)
.intercept(() -> {
actor.tell("cause-error", getTestActor());
return expectMsg(Duration.ofSeconds(1), "error-handled");
});
}};
}
@Test
public void testExceptionFiltering() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(FaultyActor.class));
// Filter specific exception type
EventFilter.forException(RuntimeException.class, system)
.occurrences(1)
.intercept(() -> {
actor.tell("throw-exception", getTestActor());
return null;
});
}};
}
}public class AdvancedEventFilterTest {
@Test
public void testChainedFiltering() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(VerboseActor.class));
// Chain multiple filter conditions
EventFilter.info(system)
.source("akka://TestSystem/user/verbose-actor")
.message("Processing started")
.occurrences(1)
.intercept(() -> {
actor.tell("start-processing", getTestActor());
return expectMsg(Duration.ofSeconds(1), "processing-started");
});
}};
}
@Test
public void testMultipleFilters() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(MultiLogActor.class));
// Multiple filters for different log levels
EventFilter.warning(system).occurrences(1).intercept(() -> {
return EventFilter.error(system).occurrences(1).intercept(() -> {
actor.tell("log-multiple", getTestActor());
return expectMsg(Duration.ofSeconds(1), "logged");
});
});
}};
}
}import akka.testkit.TestProbe;
public class TestProbeExample {
@Test
public void testWithProbe() {
new TestKit(system) {{
// Create test probe
TestProbe probe = TestProbe.create(system);
ActorRef actor = system.actorOf(Props.create(InteractionActor.class));
// Register probe as listener
actor.tell(new RegisterListener(probe.ref()), getTestActor());
expectMsg(Duration.ofSeconds(1), "listener-registered");
// Trigger notification
actor.tell("notify-listeners", getTestActor());
// Probe receives notification
probe.expectMsg(Duration.ofSeconds(1), "notification");
// Probe can reply
probe.reply("acknowledged");
expectMsg(Duration.ofSeconds(1), "ack-received");
}};
}
}public class JavaTestKit {
public JavaTestKit(ActorSystem system) { }
// Legacy methods using Scala Duration
public Object expectMsg(scala.concurrent.duration.Duration duration, Object obj)
public Object expectMsgClass(scala.concurrent.duration.Duration duration, Class<?> clazz)
public void expectNoMsg(scala.concurrent.duration.Duration duration)
// Static utility methods
public static void shutdownActorSystem(ActorSystem system)
public static void shutdownActorSystem(ActorSystem system, scala.concurrent.duration.Duration duration)
public static void shutdownActorSystem(ActorSystem system, scala.concurrent.duration.Duration duration, boolean verifySystemShutdown)
}Note: JavaTestKit is deprecated in favor of the modern javadsl.TestKit.
// Old way (deprecated)
import akka.testkit.JavaTestKit;
import scala.concurrent.duration.Duration;
public class OldStyleTest extends JavaTestKit {
public OldStyleTest() {
super(system);
}
@Test
public void oldTest() {
ActorRef actor = system.actorOf(Props.create(MyActor.class));
actor.tell("message", getRef());
expectMsg(Duration.create(1, "second"), "response");
}
}
// New way (recommended)
import akka.testkit.javadsl.TestKit;
import java.time.Duration;
public class NewStyleTest {
@Test
public void newTest() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(MyActor.class));
actor.tell("message", getTestActor());
expectMsg(Duration.ofSeconds(1), "response");
}};
}
}import java.util.concurrent.CompletableFuture;
public class AsyncTest {
@Test
public void testAsyncOperations() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(AsyncActor.class));
// Use CompletableFuture with TestKit
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
actor.tell("async-request", getTestActor());
return (String) expectMsg(Duration.ofSeconds(5), "async-response");
});
String result = future.join();
assertEquals("async-response", result);
}};
}
}import java.util.stream.IntStream;
public class StreamTest {
@Test
public void testWithStreams() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(CounterActor.class));
// Send multiple messages using streams
IntStream.range(1, 6)
.forEach(i -> actor.tell("increment", getTestActor()));
// Expect multiple responses
IntStream.range(1, 6)
.forEach(expected -> {
Integer count = (Integer) expectMsgClass(Duration.ofSeconds(1), Integer.class);
assertEquals(expected, count.intValue());
});
}};
}
}public class LambdaTest {
@Test
public void testWithLambdas() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(ProcessorActor.class));
// Use lambda for within block
String result = (String) within(Duration.ofSeconds(3), () -> {
actor.tell("process", getTestActor());
return expectMsg(Duration.ofSeconds(2), "processed");
});
// Use lambda for condition waiting
awaitCond(Duration.ofSeconds(5), () -> {
actor.tell("is-ready", getTestActor());
Object response = receiveOne(Duration.ofMillis(100));
return "ready".equals(response);
});
}};
}
}// Good: Proper test structure
public class WellStructuredTest {
private static ActorSystem system = ActorSystem.create("TestSystem");
@Test
public void testFeature() {
new TestKit(system) {{
// Test logic here
ActorRef actor = system.actorOf(Props.create(MyActor.class));
actor.tell("test", getTestActor());
String response = (String) expectMsg(Duration.ofSeconds(1), "expected");
assertEquals("Should receive correct response", "expected", response);
}};
}
@AfterClass
public static void cleanup() {
TestKit.shutdownActorSystem(system);
}
}// Good: Comprehensive error handling
@Test
public void testErrorConditions() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(FaultyActor.class));
try {
EventFilter.forException(IllegalStateException.class, system)
.occurrences(1)
.intercept(() -> {
actor.tell("invalid-state", getTestActor());
return null;
});
} catch (Exception e) {
fail("Expected IllegalStateException was not thrown: " + e.getMessage());
}
// Verify actor recovers
actor.tell("valid-request", getTestActor());
expectMsg(Duration.ofSeconds(1), "recovered");
}};
}expectMsgClass() over expectMsg() when possible// Good: Type-safe testing
@Test
public void testTypedMessages() {
new TestKit(system) {{
ActorRef actor = system.actorOf(Props.create(TypedActor.class));
actor.tell("get-status", getTestActor());
StatusMessage status = expectMsgClass(Duration.ofSeconds(1), StatusMessage.class);
assertNotNull("Status should not be null", status);
assertTrue("Status should be active", status.isActive());
assertEquals("Count should match", 42, status.getCount());
}};
}