This is an automated email from the ASF dual-hosted git repository. hepin pushed a commit to branch askBehivorKit in repository https://gitbox.apache.org/repos/asf/pekko.git
commit c25a074ea1b0980168342aae72464d038bae64c5 Author: He-Pin <[email protected]> AuthorDate: Sat Nov 8 14:24:47 2025 +0800 feat: Add asking support to BehaviorTestKit --- .../add-ask-behavior-methods.excludes | 22 ++++ .../typed/internal/BehaviorTestKitImpl.scala | 24 ++++ .../testkit/typed/internal/TestInboxImpl.scala | 121 ++++++++++++++++++++- .../testkit/typed/javadsl/BehaviorTestKit.scala | 54 +++++++++ .../actor/testkit/typed/javadsl/TestInbox.scala | 84 +++++++++++++- .../testkit/typed/scaladsl/BehaviorTestKit.scala | 18 +++ .../actor/testkit/typed/scaladsl/TestInbox.scala | 94 ++++++++++++++++ .../testkit/typed/javadsl/BehaviorTestKitTest.java | 14 +-- .../typed/scaladsl/BehaviorTestKitSpec.scala | 26 ++--- 9 files changed, 434 insertions(+), 23 deletions(-) diff --git a/actor-testkit-typed/src/main/mima-filters/2.0.x.backwards.excludes/add-ask-behavior-methods.excludes b/actor-testkit-typed/src/main/mima-filters/2.0.x.backwards.excludes/add-ask-behavior-methods.excludes new file mode 100644 index 0000000000..0616326648 --- /dev/null +++ b/actor-testkit-typed/src/main/mima-filters/2.0.x.backwards.excludes/add-ask-behavior-methods.excludes @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Add ask behavior methods +ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.actor.testkit.typed.javadsl.BehaviorTestKit.runAsk") +ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.actor.testkit.typed.javadsl.BehaviorTestKit.runAskWithStatus") +ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.actor.testkit.typed.scaladsl.BehaviorTestKit.runAsk") +ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.actor.testkit.typed.scaladsl.BehaviorTestKit.runAskWithStatus") \ No newline at end of file diff --git a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/BehaviorTestKitImpl.scala b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/BehaviorTestKitImpl.scala index 29e3c371cb..b231c5c554 100644 --- a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/BehaviorTestKitImpl.scala +++ b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/BehaviorTestKitImpl.scala @@ -31,6 +31,9 @@ import pekko.actor.typed.internal.AdaptWithRegisteredMessageAdapter import pekko.actor.typed.receptionist.Receptionist import pekko.actor.typed.scaladsl.Behaviors import pekko.annotation.InternalApi +import pekko.japi.function.{ Function => JFunction } +import pekko.pattern.StatusReply +import pekko.util.OptionVal /** * INTERNAL API @@ -62,6 +65,27 @@ private[pekko] final class BehaviorTestKitImpl[T]( // execute any future tasks scheduled in Actor's constructor runAllTasks() + override def runAsk[Res](f: ActorRef[Res] => T): ReplyInboxImpl[Res] = { + val replyToInbox = TestInboxImpl[Res]("replyTo") + + run(f(replyToInbox.ref)) + new ReplyInboxImpl(OptionVal(replyToInbox)) + } + + override def runAsk[Res](messageFactory: JFunction[ActorRef[Res], T]): ReplyInboxImpl[Res] = + runAsk(messageFactory.apply _) + + override def runAskWithStatus[Res](f: ActorRef[StatusReply[Res]] => T): StatusReplyInboxImpl[Res] = { + val replyToInbox = TestInboxImpl[StatusReply[Res]]("replyTo") + + run(f(replyToInbox.ref)) + new StatusReplyInboxImpl(OptionVal(replyToInbox)) + } + + override def runAskWithStatus[Res]( + messageFactory: JFunction[ActorRef[StatusReply[Res]], T]): StatusReplyInboxImpl[Res] = + runAskWithStatus(messageFactory.apply _) + override def retrieveEffect(): Effect = context.effectQueue.poll() match { case null => NoEffects case x => x diff --git a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/TestInboxImpl.scala b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/TestInboxImpl.scala index ae0f1f26a8..9dc91cc678 100644 --- a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/TestInboxImpl.scala +++ b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/internal/TestInboxImpl.scala @@ -19,9 +19,11 @@ import scala.annotation.tailrec import scala.collection.immutable import org.apache.pekko -import pekko.actor.ActorPath +import pekko.actor.{ ActorPath, Address, RootActorPath } import pekko.actor.typed.ActorRef import pekko.annotation.InternalApi +import pekko.pattern.StatusReply +import pekko.util.OptionVal /** * INTERNAL API @@ -63,3 +65,120 @@ private[pekko] final class TestInboxImpl[T](path: ActorPath) @InternalApi private[pekko] def as[U]: TestInboxImpl[U] = this.asInstanceOf[TestInboxImpl[U]] } + +/** + * INTERNAL API + */ +@InternalApi +object TestInboxImpl { + def apply[T](name: String): TestInboxImpl[T] = { + new TestInboxImpl(address / name) + } + + private[pekko] val address = RootActorPath(Address("pekko.actor.typed.inbox", "anonymous")) +} + +/** + * INTERNAL API + */ +@InternalApi +private[pekko] final class ReplyInboxImpl[T](private var underlying: OptionVal[TestInboxImpl[T]]) + extends pekko.actor.testkit.typed.javadsl.ReplyInbox[T] + with pekko.actor.testkit.typed.scaladsl.ReplyInbox[T] { + + def receiveReply(): T = + underlying match { + case OptionVal.Some(testInbox) => + underlying = OptionVal.None + testInbox.receiveMessage() + + case _ => throw new AssertionError("Reply was already received") + } + + def expectReply(expectedReply: T): Unit = + receiveReply() match { + case matches if matches == expectedReply => () + case doesntMatch => + throw new AssertionError(s"Expected $expectedReply but received $doesntMatch") + } + + def expectNoReply(): ReplyInboxImpl[T] = + underlying match { + case OptionVal.Some(testInbox) if testInbox.hasMessages => + throw new AssertionError(s"Expected no reply, but ${receiveReply()} was received") + + case OptionVal.Some(_) => this + + case _ => + // already received the reply, so this expectation shouldn't even be made + throw new AssertionError("Improper expectation of no reply: reply was already received") + } + + def hasReply: Boolean = + underlying match { + case OptionVal.Some(testInbox) => testInbox.hasMessages + case _ => false + } +} + +/** + * INTERNAL API + */ +@InternalApi +private[pekko] final class StatusReplyInboxImpl[T](private var underlying: OptionVal[TestInboxImpl[StatusReply[T]]]) + extends pekko.actor.testkit.typed.javadsl.StatusReplyInbox[T] + with pekko.actor.testkit.typed.scaladsl.StatusReplyInbox[T] { + + def receiveStatusReply(): StatusReply[T] = + underlying match { + case OptionVal.Some(testInbox) => + underlying = OptionVal.None + testInbox.receiveMessage() + + case _ => throw new AssertionError("Reply was already received") + } + + def receiveValue(): T = + receiveStatusReply() match { + case StatusReply.Success(v) => v.asInstanceOf[T] + case err => throw new AssertionError(s"Expected a successful reply but received $err") + } + + def receiveError(): Throwable = + receiveStatusReply() match { + case StatusReply.Error(t) => t + case success => throw new AssertionError(s"Expected an error reply but received $success") + } + + def expectValue(expectedValue: T): Unit = + receiveValue() match { + case matches if matches == expectedValue => () + case doesntMatch => + throw new AssertionError(s"Expected $expectedValue but received $doesntMatch") + } + + def expectErrorMessage(errorMessage: String): Unit = + receiveError() match { + case matches if matches.getMessage == errorMessage => () + case doesntMatch => + throw new AssertionError(s"Expected a throwable with message $errorMessage, but got ${doesntMatch.getMessage}") + } + + def expectNoReply(): StatusReplyInboxImpl[T] = + underlying match { + case OptionVal.Some(testInbox) if testInbox.hasMessages => + throw new AssertionError(s"Expected no reply, but ${receiveStatusReply()} was received") + + case OptionVal.Some(_) => this + + case _ => + // already received the reply, so this expectation shouldn't even be made + throw new AssertionError("Improper expectation of no reply: reply was already received") + } + + def hasReply: Boolean = + underlying match { + case OptionVal.Some(testInbox) => testInbox.hasMessages + case _ => false + } +} diff --git a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKit.scala b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKit.scala index 20909fc06b..0623c5914c 100644 --- a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKit.scala +++ b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKit.scala @@ -15,12 +15,16 @@ package org.apache.pekko.actor.testkit.typed.javadsl import java.util.concurrent.ThreadLocalRandom +import scala.annotation.nowarn + import org.apache.pekko import pekko.actor.testkit.typed.{ CapturedLogEvent, Effect } import pekko.actor.testkit.typed.internal.{ ActorSystemStub, BehaviorTestKitImpl } import pekko.actor.typed.{ ActorRef, Behavior, Signal } import pekko.actor.typed.receptionist.Receptionist import pekko.annotation.{ ApiMayChange, DoNotInherit } +import pekko.japi.function.{ Function => JFunction } +import pekko.pattern.StatusReply import com.typesafe.config.Config @@ -71,6 +75,56 @@ object BehaviorTestKit { @ApiMayChange abstract class BehaviorTestKit[T] { + /** + * Constructs a message using the provided 'messageFactory' to inject a single-use "reply to" + * [[akka.actor.typed.ActorRef]], and sends the constructed message to the behavior, recording any [[Effect]]s. + * + * The returned [[ReplyInbox]] allows the message sent to the "reply to" `ActorRef` to be asserted on. + * + * @since 1.3.0 + */ + def runAsk[Res](messageFactory: JFunction[ActorRef[Res], T]): ReplyInbox[Res] + + /** + * The same as [[runAsk]], but with the response class specified. This improves type inference in Java + * when asserting on the reply in the same statement as the `runAsk` as in: + * + * ``` + * testkit.runAsk(Done.class, DoSomethingCommand::new).expectReply(Done.getInstance()); + * ``` + * + * If explicitly saving the [[ReplyInbox]] in a variable, the version without the class may be preferred. + * + * @since 1.3.0 + */ + @nowarn("msg=never used") // responseClass is a pretend param to guide inference + def runAsk[Res](responseClass: Class[Res], messageFactory: JFunction[ActorRef[Res], T]): ReplyInbox[Res] = + runAsk(messageFactory) + + /** + * The same as [[runAsk]] but only for requests that result in a response of type [[akka.pattern.StatusReply]]. + * + * @since 1.3.0 + */ + def runAskWithStatus[Res](messageFactory: JFunction[ActorRef[StatusReply[Res]], T]): StatusReplyInbox[Res] + + /** + * The same as [[runAskWithStatus]], but with the response class specified. This improves type inference in + * Java when asserting on the reply in the same statement as the `runAskWithStatus` as in: + * + * ``` + * testkit.runAskWithStatus(Done.class, DoSomethingWithStatusCommand::new).expectValue(Done.getInstance()); + * ``` + * + * If explicitly saving the [[StatusReplyInbox]] in a variable, the version without the class may be preferred. + * + * @since 1.3.0 + */ + @nowarn("msg=never used") // responseClass is a pretend param to guide inference + def runAskWithStatus[Res](responseClass: Class[Res], + messageFactory: JFunction[ActorRef[StatusReply[Res]], T]): StatusReplyInbox[Res] = + runAskWithStatus(messageFactory) + /** * Requests the oldest [[Effect]] or [[pekko.actor.testkit.typed.javadsl.Effects.noEffects]] if no effects * have taken place. The effect is consumed, subsequent calls won't diff --git a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/TestInbox.scala b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/TestInbox.scala index ca3ff85c4e..962083dc9b 100644 --- a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/TestInbox.scala +++ b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/javadsl/TestInbox.scala @@ -21,7 +21,8 @@ import scala.jdk.CollectionConverters._ import org.apache.pekko import pekko.actor.testkit.typed.internal.TestInboxImpl import pekko.actor.typed.ActorRef -import pekko.annotation.DoNotInherit +import pekko.annotation.{ ApiMayChange, DoNotInherit } +import pekko.pattern.StatusReply object TestInbox { import pekko.actor.testkit.typed.scaladsl.TestInbox.address @@ -76,3 +77,84 @@ abstract class TestInbox[T] { // TODO expectNoMsg etc } + +/** + * Similar to an [[akka.actor.testkit.typed.javadsl.TestInbox]], but can only ever give access to a single message (a reply). + * + * Not intended for user creation: the [[akka.actor.testkit.typed.javadsl.BehaviorTestKit]] will provide these to + * denote that at most a single reply is expected. + * + * @since 1.3.0 + */ +@DoNotInherit +@ApiMayChange +trait ReplyInbox[T] { + + /** + * Get and remove the reply. Subsequent calls to `receiveReply`, `expectReply`, and `expectNoReply` will fail and `hasReplies` + * will be false after calling this method + */ + def receiveReply(): T + + /** + * Assert and remove the message. Subsequent calls to `receiveReply`, `expectReply`, and `expectNoReply` will fail and `hasReplies` + * will be false after calling this method + */ + def expectReply(expectedReply: T): Unit + + def expectNoReply(): ReplyInbox[T] + def hasReply: Boolean +} + +/** + * A [[akka.actor.testkit.typed.javadsl.ReplyInbox]] which specially handles [[akka.pattern.StatusReply]]. + * + * Note that there is no provided ability to expect a specific `Throwable`, as it's recommended to prefer + * a string error message or to enumerate failures with specific types. + * + * Not intended for user creation: the [[akka.actor.testkit.typed.javadsl.BehaviorTestKit]] will provide these to + * denote that at most a single reply is expected. + */ +@DoNotInherit +@ApiMayChange +trait StatusReplyInbox[T] { + + /** + * Get and remove the status reply. Subsequent calls to any `receive` or `expect` method will fail and `hasReply` + * will be false after calling this method. + */ + def receiveStatusReply(): StatusReply[T] + + /** + * Get and remove the successful value of the status reply. This will fail if the status reply is an error. + * Subsequent calls to any `receive` or `expect` method will fail and `hasReply` will be false after calling this + * method. + */ + def receiveValue(): T + + /** + * Get and remove the error value of the status reply. This will fail if the status reply is a success. + * Subsequent calls to any `receive` or `expect` method will fail and `hasReply` will be false after calling this + * method. + */ + def receiveError(): Throwable + + /** + * Assert that the status reply is a success with this value and remove the status reply. Subsequent calls to any + * `receive` or `expect` method will fail and `hasReply` will be false after calling this method. + */ + def expectValue(expectedValue: T): Unit + + /** + * Assert that the status reply is a failure with this error message and remove the status reply. Subsequent + * calls to any `receive` or `expect` method will fail and `hasReply` will be false after calling this method. + */ + def expectErrorMessage(errorMessage: String): Unit + + /** + * Assert that this inbox has *never* received a reply. + */ + def expectNoReply(): StatusReplyInbox[T] + + def hasReply: Boolean +} diff --git a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKit.scala b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKit.scala index 9dd5efe821..e3f7945c64 100644 --- a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKit.scala +++ b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKit.scala @@ -24,6 +24,7 @@ import pekko.actor.testkit.typed.internal.{ ActorSystemStub, BehaviorTestKitImpl import pekko.actor.typed.{ ActorRef, Behavior, Signal, TypedActorContext } import pekko.actor.typed.receptionist.Receptionist import pekko.annotation.{ ApiMayChange, DoNotInherit } +import pekko.pattern.StatusReply import com.typesafe.config.Config @@ -57,6 +58,23 @@ object BehaviorTestKit { @ApiMayChange trait BehaviorTestKit[T] { + /** + * Constructs a message using the provided function to inject a single-use "reply to" [[akka.actor.typed.ActorRef]], + * and sends the constructed message to the behavior, recording any [[Effect]]s. + * + * The returned [[ReplyInbox]] allows the message sent to the "reply to" `ActorRef` to be asserted on. + * + * @since 1.3.0 + */ + def runAsk[Res](f: ActorRef[Res] => T): ReplyInbox[Res] + + /** + * The same as [[runAsk]] but only for requests that result in a response of type [[akka.pattern.StatusReply]]. + * + * @since 1.3.0 + */ + def runAskWithStatus[Res](f: ActorRef[StatusReply[Res]] => T): StatusReplyInbox[Res] + // FIXME it is weird that this is public but it is used in BehaviorSpec, could we avoid that? private[pekko] def context: TypedActorContext[T] diff --git a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/TestInbox.scala b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/TestInbox.scala index 9f66b97a43..6c1a1935aa 100644 --- a/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/TestInbox.scala +++ b/actor-testkit-typed/src/main/scala/org/apache/pekko/actor/testkit/typed/scaladsl/TestInbox.scala @@ -18,10 +18,12 @@ import java.util.concurrent.ThreadLocalRandom import scala.collection.immutable import org.apache.pekko +import pekko.Done import pekko.actor.{ Address, RootActorPath } import pekko.actor.testkit.typed.internal.TestInboxImpl import pekko.actor.typed.ActorRef import pekko.annotation.{ ApiMayChange, DoNotInherit } +import pekko.pattern.StatusReply @ApiMayChange object TestInbox { @@ -74,3 +76,95 @@ trait TestInbox[T] { // TODO expectNoMsg etc } + +/** + * Similar to an [[akka.actor.testkit.typed.scaladsl.TestInbox]], but can only ever give access to a single message (a reply). + * + * Not intended for user creation: the [[akka.actor.testkit.typed.scaladsl.BehaviorTestKit]] will provide these + * to denote that at most a single reply is expected. + * + * @since 1.3.0 + */ +@DoNotInherit +@ApiMayChange +trait ReplyInbox[T] { + + /** + * Get and remove the reply. Subsequent calls to `receiveReply`, `expectReply`, and `expectNoReply` will fail and `hasReply` + * will be false after calling this method + */ + def receiveReply(): T + + /** + * Assert and remove the reply. Subsequent calls to `receiveReply`, `expectReply`, and `expectNoReply` will fail and `hasReply` + * will be false after calling this method + */ + def expectReply(expectedReply: T): Unit + + /** + * Assert that this inbox has *never* received a reply. + */ + def expectNoReply(): ReplyInbox[T] + + def hasReply: Boolean +} + +/** + * A [[akka.actor.testkit.typed.scaladsl.ReplyInbox]] which specially handles [[akka.pattern.StatusReply]]. + * + * Note that there is no provided ability to expect a specific `Throwable`, as it's recommended to prefer + * a string error message or to enumerate failures with specific types. + * + * Not intended for user creation: the [[akka.actor.testkit.typed.scaladsl.BehaviorTestKit]] will provide these + * to denote that at most a single reply is expected. + */ +@DoNotInherit +@ApiMayChange +trait StatusReplyInbox[T] { + + /** + * Get and remove the status reply. Subsequent calls to any `receive` or `expect` method will fail and `hasReply` + * will be false after calling this method. + */ + def receiveStatusReply(): StatusReply[T] + + /** + * Get and remove the successful value of the status reply. This will fail if the status reply is an error. + * Subsequent calls to any `receive` or `expect` method will fail and `hasReply` will be false after calling this + * method. + */ + def receiveValue(): T + + /** + * Get and remove the error value of the status reply. This will fail if the status reply is a success. + * Subsequent calls to any `receive` or `expect` method will fail and `hasReply` will be false after calling this + * method. + */ + def receiveError(): Throwable + + /** + * Assert that the status reply is a success with this value and remove the status reply. Subsequent calls to any + * `receive` or `expect` method will fail and `hasReply` will be false after calling this method. + */ + def expectValue(expectedValue: T): Unit + + /** + * Assert that the status reply is a failure with this error message and remove the status reply. Subsequent + * calls to any `receive` or `expect` method will fail and `hasReply` will be false after calling this method. + */ + def expectErrorMessage(errorMessage: String): Unit + + /** + * Assert that the successful value of the status reply is [[akka.Done]]. Subsequent calls to any `receive` or + * `expect` method will fail and `hasReply` will be false after calling this method. + */ + @annotation.nowarn("msg=never used") + def expectDone()(implicit ev: T =:= Done): Unit = expectValue(Done.asInstanceOf[T]) + + /** + * Assert that this inbox has *never* received a reply. + */ + def expectNoReply(): StatusReplyInbox[T] + + def hasReply: Boolean +} diff --git a/actor-testkit-typed/src/test/java/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKitTest.java b/actor-testkit-typed/src/test/java/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKitTest.java index 549742b4a4..5c1eb908f0 100644 --- a/actor-testkit-typed/src/test/java/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKitTest.java +++ b/actor-testkit-typed/src/test/java/org/apache/pekko/actor/testkit/typed/javadsl/BehaviorTestKitTest.java @@ -356,12 +356,13 @@ public class BehaviorTestKitTest extends JUnitSuite { @Test public void allowRetrievingAndKilling() { BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior); - TestInbox<ActorRef<String>> i = TestInbox.create(); TestInbox<String> h = TestInbox.create(); - test.run(new SpawnSession(i.getRef(), h.getRef())); - ActorRef<String> sessionRef = i.receiveMessage(); - assertFalse(i.hasMessages()); + ReplyInbox<ActorRef<String>> sessionReply = + test.runAsk(replyTo -> new SpawnSession(replyTo, h.getRef())); + + ActorRef<String> sessionRef = sessionReply.receiveReply(); + Effect.SpawnedAnonymous s = test.expectEffectClass(Effect.SpawnedAnonymous.class); assertEquals(sessionRef, s.ref()); @@ -369,10 +370,9 @@ public class BehaviorTestKitTest extends JUnitSuite { session.run("hello"); assertEquals(Collections.singletonList("hello"), h.getAllReceived()); - TestInbox<Done> d = TestInbox.create(); - test.run(new KillSession(sessionRef, d.getRef())); + ReplyInbox<Done> doneReply = test.runAsk(replyTo -> new KillSession(sessionRef, replyTo)); + doneReply.expectReply(Done.getInstance()); - assertEquals(Collections.singletonList(Done.getInstance()), d.getAllReceived()); test.expectEffectClass(Effect.Stopped.class); } diff --git a/actor-testkit-typed/src/test/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala b/actor-testkit-typed/src/test/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala index d5497952b1..09e3c499c9 100644 --- a/actor-testkit-typed/src/test/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala +++ b/actor-testkit-typed/src/test/scala/org/apache/pekko/actor/testkit/typed/scaladsl/BehaviorTestKitSpec.scala @@ -374,12 +374,11 @@ class BehaviorTestKitSpec extends AnyWordSpec with Matchers with LogCapturing { "BehaviorTestKit’s child actor support" must { "allow retrieving and killing" in { val testkit = BehaviorTestKit(Parent.init) - val i = TestInbox[ActorRef[String]]() val h = TestInbox[String]() - testkit.run(SpawnSession(i.ref, h.ref)) - val sessionRef = i.receiveMessage() - i.hasMessages shouldBe false + val sessionRef = + testkit.runAsk[ActorRef[String]](SpawnSession(_, h.ref)).receiveReply() + val s = testkit.expectEffectType[SpawnedAnonymous[_]] // must be able to get the created ref, even without explicit reply s.ref shouldBe sessionRef @@ -388,10 +387,8 @@ class BehaviorTestKitSpec extends AnyWordSpec with Matchers with LogCapturing { session.run("hello") h.receiveAll() shouldBe Seq("hello") - val d = TestInbox[Done]() - testkit.run(KillSession(sessionRef, d.ref)) + testkit.runAsk(KillSession(sessionRef, _)).expectReply(Done) - d.receiveAll() shouldBe Seq(Done) testkit.expectEffectType[Stopped] } @@ -426,9 +423,9 @@ class BehaviorTestKitSpec extends AnyWordSpec with Matchers with LogCapturing { "timer support" must { "schedule and cancel timers" in { val testkit = BehaviorTestKit[Parent.Command](Parent.init) - val t = TestInbox[Boolean]() - testkit.run(IsTimerActive("abc", t.ref)) - t.receiveMessage() shouldBe false + + testkit.runAsk(IsTimerActive("abc", _)).expectReply(false) + testkit.run(ScheduleCommand("abc", 42.seconds, Effect.TimerScheduled.SingleMode, SpawnChild)) testkit.expectEffectPF { case Effect.TimerScheduled( @@ -439,15 +436,16 @@ class BehaviorTestKitSpec extends AnyWordSpec with Matchers with LogCapturing { false /*not overriding*/ ) => finiteDuration should equal(42.seconds) } - testkit.run(IsTimerActive("abc", t.ref)) - t.receiveMessage() shouldBe true + + testkit.runAsk(IsTimerActive("abc", _)).expectReply(true) + testkit.run(CancelScheduleCommand("abc")) testkit.expectEffectPF { case Effect.TimerCancelled(key) => key should equal("abc") } - testkit.run(IsTimerActive("abc", t.ref)) - t.receiveMessage() shouldBe false + + testkit.runAsk(IsTimerActive("abc", _)).expectReply(false) } "schedule and fire timers" in { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
