This is an automated email from the ASF dual-hosted git repository.
hepin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 82692c8646 feat: Add asking support to BehaviorTestKit (#2453)
82692c8646 is described below
commit 82692c86467b212d3d8525a3bad4aa608c952446
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Sun Nov 9 13:54:12 2025 +0800
feat: Add asking support to BehaviorTestKit (#2453)
---
.../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..7dcbf1fa06
--- /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")
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 d9c3aa8686..941b14ecf4 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.{ AdaptMessage,
AdaptWithRegisteredMessageAdap
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 43d01da6d9..049208f6a3 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
@@ -402,12 +402,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());
@@ -415,10 +416,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 844c1cee6f..f85c92f35e 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
@@ -396,12 +396,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
@@ -410,10 +409,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]
}
@@ -448,9 +445,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(
@@ -461,15 +458,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]