Expose Effect classes for BehaviorTestKit Java DSL, #25070, #24781

* Expose Effect classes for BehaviorTestKit Java DSL

- Share effect classes between java and scala dsl
- make spawn adapter private
- add message adapter effect
- add tests for java behavior test kit

* Mirror Effect factory in java and scala dsl
This commit is contained in:
Christopher Batey 2018-06-04 17:01:09 +01:00 committed by Patrik Nordwall
parent 356415014f
commit 2a979fe296
9 changed files with 571 additions and 152 deletions

View file

@ -4,13 +4,18 @@
package akka.actor.testkit.typed
import akka.annotation.DoNotInherit
import akka.actor.typed.{ ActorRef, Behavior, Props }
import akka.annotation.{ DoNotInherit, InternalApi }
import akka.util.JavaDurationConverters._
import scala.compat.java8.FunctionConverters._
import scala.concurrent.duration.FiniteDuration
/**
* All tracked effects for the [[akka.actor.testkit.typed.scaladsl.BehaviorTestKit]] and
* [[akka.actor.testkit.typed.javadsl.BehaviorTestKit]] must extend this type.
*
* Factories/types for effects are available through [[akka.actor.testkit.typed.scaladsl.Effects]]
* Factories/types for effects are available through [[akka.actor.testkit.typed.javadsl.Effects]]
* and [[akka.actor.testkit.typed.javadsl.Effects]]
*
* Not for user extension
@ -18,3 +23,175 @@ import akka.annotation.DoNotInherit
@DoNotInherit
abstract class Effect private[akka] ()
object Effect {
/**
* The behavior spawned a named child with the given behavior (and optionally specific props)
*/
final class Spawned[T](val behavior: Behavior[T], val childName: String, val props: Props, val ref: ActorRef[T])
extends Effect with Product3[Behavior[T], String, Props] with Serializable {
override def equals(other: Any) = other match {
case o: Spawned[_]
this.behavior == o.behavior &&
this.childName == o.childName &&
this.props == o.props
case _ false
}
override def hashCode: Int = (behavior.## * 31 + childName.##) * 31 + props.##
override def toString: String = s"Spawned($behavior, $childName, $props)"
override def productPrefix = "Spawned"
override def _1: Behavior[T] = behavior
override def _2: String = childName
override def _3: Props = props
override def canEqual(o: Any) = o.isInstanceOf[Spawned[_]]
}
object Spawned {
def apply[T](behavior: Behavior[T], childName: String, props: Props = Props.empty): Spawned[T] = new Spawned(behavior, childName, props, null)
def unapply[T](s: Spawned[T]): Option[(Behavior[T], String, Props)] = Some((s.behavior, s.childName, s.props))
}
/**
* The behavior spawned an anonymous child with the given behavior (and optionally specific props)
*/
final class SpawnedAnonymous[T](val behavior: Behavior[T], val props: Props, val ref: ActorRef[T])
extends Effect with Product2[Behavior[T], Props] with Serializable {
override def equals(other: Any) = other match {
case o: SpawnedAnonymous[_] this.behavior == o.behavior && this.props == o.props
case _ false
}
override def hashCode: Int = behavior.## * 31 + props.##
override def toString: String = s"SpawnedAnonymous($behavior, $props)"
override def productPrefix = "SpawnedAnonymous"
override def _1: Behavior[T] = behavior
override def _2: Props = props
override def canEqual(o: Any) = o.isInstanceOf[SpawnedAnonymous[_]]
}
object SpawnedAnonymous {
def apply[T](behavior: Behavior[T], props: Props = Props.empty): SpawnedAnonymous[T] = new SpawnedAnonymous(behavior, props, null)
def unapply[T](s: SpawnedAnonymous[T]): Option[(Behavior[T], Props)] = Some((s.behavior, s.props))
}
/**
* INTERNAL API
* Spawning adapters is private[akka]
*/
@InternalApi
private[akka] final class SpawnedAdapter[T](val name: String, val ref: ActorRef[T])
extends Effect with Product1[String] with Serializable {
override def equals(other: Any) = other match {
case o: SpawnedAdapter[_] this.name == o.name
case _ false
}
override def hashCode: Int = name.##
override def toString: String = s"SpawnedAdapter($name)"
override def productPrefix = "SpawnedAdapter"
override def _1: String = name
override def canEqual(o: Any) = o.isInstanceOf[SpawnedAdapter[_]]
}
/**
* INTERNAL API
* Spawning adapters is private[akka]
*/
@InternalApi
private[akka] object SpawnedAdapter {
def apply[T](name: String): SpawnedAdapter[T] = new SpawnedAdapter(name, null)
def unapply[T](s: SpawnedAdapter[T]): Option[Tuple1[String]] = Some(Tuple1(s.name))
}
/**
* INTERNAL API
* The behavior spawned an anonymous adapter, through `ctx.spawnMessageAdapter`
*/
@InternalApi
private[akka] final class SpawnedAnonymousAdapter[T](val ref: ActorRef[T])
extends Effect with Product with Serializable {
override def equals(other: Any) = other match {
case _: SpawnedAnonymousAdapter[_] true
case _ false
}
override def hashCode: Int = Nil.##
override def toString: String = "SpawnedAnonymousAdapter"
override def productPrefix = "SpawnedAnonymousAdapter"
override def productIterator = Iterator.empty
override def productArity = 0
override def productElement(n: Int) = throw new NoSuchElementException
override def canEqual(o: Any) = o.isInstanceOf[SpawnedAnonymousAdapter[_]]
}
/**
* INTERNAL API
*/
@InternalApi
private[akka] object SpawnedAnonymousAdapter {
def apply[T]() = new SpawnedAnonymousAdapter[T](null)
def unapply[T](s: SpawnedAnonymousAdapter[T]): Boolean = true
}
/**
* The behavior create a message adapter for the messages of type clazz
*/
final case class MessageAdapter[A, T](messageClass: Class[A], adapt: A T) extends Effect {
/**
* JAVA API
*/
def adaptFunction: java.util.function.Function[A, T] = adapt.asJava
}
/**
* The behavior stopped `childName`
*/
final case class Stopped(childName: String) extends Effect
/**
* The behavior started watching `other`, through `ctx.watch(other)`
*/
final case class Watched[T](other: ActorRef[T]) extends Effect
/**
* The behavior started watching `other`, through `ctx.unwatch(other)`
*/
final case class Unwatched[T](other: ActorRef[T]) extends Effect
/**
* The behavior set a new receive timeout, with `msg` as timeout notification
*/
final case class ReceiveTimeoutSet[T](d: FiniteDuration, msg: T) extends Effect {
/**
* Java API
*/
def duration(): java.time.Duration = d.asJava
}
final case object ReceiveTimeoutCancelled extends ReceiveTimeoutCancelled
sealed abstract class ReceiveTimeoutCancelled extends Effect
/**
* The behavior used `ctx.schedule` to schedule `msg` to be sent to `target` after `delay`
* FIXME what about events scheduled through the scheduler?
*/
final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect {
def duration(): java.time.Duration = delay.asJava
}
/**
* Used to represent an empty list of effects - in other words, the behavior didn't do anything observable
*/
case object NoEffects extends NoEffects
/**
* Used for NoEffects expectations by type
*/
sealed abstract class NoEffects extends Effect
}

View file

@ -11,7 +11,7 @@ import akka.actor.ActorPath
import akka.actor.typed.{ Behavior, PostStop, Signal, ActorRef }
import akka.annotation.InternalApi
import akka.actor.testkit.typed.Effect
import akka.actor.testkit.typed.scaladsl.Effects._
import akka.actor.testkit.typed.Effect._
import scala.annotation.tailrec
import scala.collection.JavaConverters._

View file

@ -5,14 +5,17 @@
package akka.actor.testkit.typed.internal
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.function
import akka.actor.{ Cancellable, ActorPath }
import akka.actor.{ ActorPath, Cancellable }
import akka.actor.typed.{ ActorRef, Behavior, Props }
import akka.annotation.InternalApi
import akka.actor.testkit.typed.Effect
import akka.actor.testkit.typed.scaladsl.Effects._
import akka.actor.testkit.typed.Effect._
import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.reflect.ClassTag
import scala.compat.java8.FunctionConverters._
/**
* INTERNAL API
@ -26,18 +29,26 @@ import scala.concurrent.duration.{ Duration, FiniteDuration }
effectQueue.offer(new SpawnedAnonymous(behavior, props, ref))
ref
}
override def spawnMessageAdapter[U](f: U T): ActorRef[U] = {
val ref = super.spawnMessageAdapter(f)
effectQueue.offer(new SpawnedAnonymousAdapter(ref))
ref
}
override def spawnMessageAdapter[U](f: U T, name: String): ActorRef[U] = {
val ref = super.spawnMessageAdapter(f, name)
effectQueue.offer(new SpawnedAdapter(name, ref))
ref
}
override def messageAdapter[U: ClassTag](f: U T): ActorRef[U] = {
val ref = super.messageAdapter(f)
effectQueue.offer(MessageAdapter(implicitly[ClassTag[U]].runtimeClass.asInstanceOf[Class[U]], f))
ref
}
override def messageAdapter[U](messageClass: Class[U], f: function.Function[U, T]): ActorRef[U] = {
val ref = super.messageAdapter(messageClass, f)
effectQueue.offer(MessageAdapter[U, T](messageClass, f.asScala))
ref
}
override def spawn[U](behavior: Behavior[U], name: String, props: Props = Props.empty): ActorRef[U] = {
val ref = super.spawn(behavior, name, props)
effectQueue.offer(new Spawned(behavior, name, props, ref))
@ -64,7 +75,7 @@ import scala.concurrent.duration.{ Duration, FiniteDuration }
super.setReceiveTimeout(d, msg)
}
override def cancelReceiveTimeout(): Unit = {
effectQueue.offer(ReceiveTimeoutSet(Duration.Undefined, null))
effectQueue.offer(ReceiveTimeoutCancelled)
super.cancelReceiveTimeout()
}
override def schedule[U](delay: FiniteDuration, target: ActorRef[U], msg: U): Cancellable = {

View file

@ -15,85 +15,69 @@ import akka.util.JavaDurationConverters._
* actual effects to expected ones.
*/
object Effects {
import akka.actor.testkit.typed.scaladsl.Effects._
import akka.actor.testkit.typed.Effect._
/**
* The behavior spawned a named child with the given behavior with no specific props
*/
def spawned[T](behavior: Behavior[T], childName: String): Effect = Spawned(behavior, childName)
def spawned[T](behavior: Behavior[T], childName: String): Spawned[T] = Spawned(behavior, childName)
/**
* The behavior spawned a named child with the given behavior with no specific props
*/
def spawned[T](behavior: Behavior[T], childName: String, ref: ActorRef[T]): Effect = new Spawned(behavior, childName, Props.empty, ref)
def spawned[T](behavior: Behavior[T], childName: String, ref: ActorRef[T]): Spawned[T] = new Spawned(behavior, childName, Props.empty, ref)
/**
* The behavior spawned a named child with the given behavior and specific props
*/
def spawned[T](behavior: Behavior[T], childName: String, props: Props): Effect = Spawned(behavior, childName, props)
def spawned[T](behavior: Behavior[T], childName: String, props: Props): Spawned[T] = Spawned(behavior, childName, props)
/**
* The behavior spawned a named child with the given behavior and specific props
*/
def spawned[T](behavior: Behavior[T], childName: String, props: Props, ref: ActorRef[T]): Effect = new Spawned(behavior, childName, props, ref)
def spawned[T](behavior: Behavior[T], childName: String, props: Props, ref: ActorRef[T]): Spawned[T] = new Spawned(behavior, childName, props, ref)
/**
* The behavior spawned an anonymous child with the given behavior with no specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T]): Effect = SpawnedAnonymous(behavior)
def spawnedAnonymous[T](behavior: Behavior[T]): SpawnedAnonymous[T] = SpawnedAnonymous(behavior)
/**
* The behavior spawned an anonymous child with the given behavior with no specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T], ref: ActorRef[T]): Effect = new SpawnedAnonymous(behavior, Props.empty, ref)
def spawnedAnonymous[T](behavior: Behavior[T], ref: ActorRef[T]): SpawnedAnonymous[T] = new SpawnedAnonymous(behavior, Props.empty, ref)
/**
* The behavior spawned an anonymous child with the given behavior with specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T], props: Props): Effect = SpawnedAnonymous(behavior, props)
def spawnedAnonymous[T](behavior: Behavior[T], props: Props): SpawnedAnonymous[T] = SpawnedAnonymous(behavior, props)
/**
* The behavior spawned an anonymous child with the given behavior with specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T], props: Props, ref: ActorRef[T]): Effect = new SpawnedAnonymous(behavior, props, ref)
/**
* The behavior spawned an anonymous adapter, through `ctx.spawnMessageAdapter`
*/
def spawnedAnonymousAdapter(): Effect = SpawnedAnonymousAdapter[Any]()
/**
* The behavior spawned an anonymous adapter, through `ctx.spawnMessageAdapter`
*/
def spawnedAnonymousAdapter[T](ref: ActorRef[T]): Effect = new SpawnedAnonymousAdapter(ref)
/**
* The behavior spawned a named adapter, through `ctx.spawnMessageAdapter`
*/
def spawnedAdapter(name: String): Effect = SpawnedAdapter[Any](name)
/**
* The behavior spawned a named adapter, through `ctx.spawnMessageAdapter`
*/
def spawnedAdapter[T](name: String, ref: ActorRef[T]): Effect = new SpawnedAdapter(name, ref)
def spawnedAnonymous[T](behavior: Behavior[T], props: Props, ref: ActorRef[T]): SpawnedAnonymous[T] = new SpawnedAnonymous(behavior, props, ref)
/**
* The behavior stopped `childName`
*/
def stopped(childName: String): Effect = Stopped(childName)
def stopped(childName: String): Stopped = Stopped(childName)
/**
* The behavior started watching `other`, through `ctx.watch(other)`
*/
def watched[T](other: ActorRef[T]): Effect = Watched(other)
def watched[T](other: ActorRef[T]): Watched[T] = Watched(other)
/**
* The behavior started watching `other`, through `ctx.unwatch(other)`
*/
def unwatched[T](other: ActorRef[T]): Effect = Unwatched(other)
def unwatched[T](other: ActorRef[T]): Unwatched[T] = Unwatched(other)
/**
* The behavior set a new receive timeout, with `msg` as timeout notification
*/
def receiveTimeoutSet[T](d: Duration, msg: T): Effect = ReceiveTimeoutSet(d.asScala, msg)
def receiveTimeoutSet[T](d: Duration, msg: T): ReceiveTimeoutSet[T] = ReceiveTimeoutSet(d.asScala, msg)
/**
* The behavior used `ctx.schedule` to schedule `msg` to be sent to `target` after `delay`
* FIXME what about events scheduled through the scheduler?
*/
def scheduled[U](delay: Duration, target: ActorRef[U], msg: U): Effect =
def scheduled[U](delay: Duration, target: ActorRef[U], msg: U): Scheduled[U] =
Scheduled(delay.asScala, target, msg)
/**
* Used to represent an empty list of effects - in other words, the behavior didn't do anything observable
*/
def noEffects(): Effect = NoEffects
def noEffects(): NoEffects = NoEffects
}

View file

@ -5,142 +5,77 @@
package akka.actor.testkit.typed.scaladsl
import akka.actor.typed.{ ActorRef, Behavior, Props }
import akka.actor.testkit.typed.Effect
import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.concurrent.duration.FiniteDuration
/**
* Types for behavior effects for [[BehaviorTestKit]], each effect has a suitable equals and can be used to compare
* Factories for behavior effects for [[BehaviorTestKit]], each effect has a suitable equals and can be used to compare
* actual effects to expected ones.
*/
object Effects {
import akka.actor.testkit.typed.Effect._
/**
* The behavior spawned a named child with the given behavior (and optionally specific props)
* The behavior spawned a named child with the given behavior with no specific props
*/
final class Spawned[T](val behavior: Behavior[T], val childName: String, val props: Props, val ref: ActorRef[T])
extends Effect with Product3[Behavior[T], String, Props] with Serializable {
override def equals(other: Any) = other match {
case o: Spawned[_]
this.behavior == o.behavior &&
this.childName == o.childName &&
this.props == o.props
case _ false
}
override def hashCode: Int = (behavior.## * 31 + childName.##) * 31 + props.##
override def toString: String = s"Spawned($behavior, $childName, $props)"
override def productPrefix = "Spawned"
override def _1: Behavior[T] = behavior
override def _2: String = childName
override def _3: Props = props
override def canEqual(o: Any) = o.isInstanceOf[Spawned[_]]
}
object Spawned {
def apply[T](behavior: Behavior[T], childName: String, props: Props = Props.empty): Spawned[T] = new Spawned(behavior, childName, props, null)
def unapply[T](s: Spawned[T]): Option[(Behavior[T], String, Props)] = Some((s.behavior, s.childName, s.props))
}
def spawned[T](behavior: Behavior[T], childName: String): Spawned[T] = Spawned(behavior, childName)
/**
* The behavior spawned an anonymous child with the given behavior (and optionally specific props)
* The behavior spawned a named child with the given behavior with no specific props
*/
final class SpawnedAnonymous[T](val behavior: Behavior[T], val props: Props, val ref: ActorRef[T])
extends Effect with Product2[Behavior[T], Props] with Serializable {
override def equals(other: Any) = other match {
case o: SpawnedAnonymous[_] this.behavior == o.behavior && this.props == o.props
case _ false
}
override def hashCode: Int = behavior.## * 31 + props.##
override def toString: String = s"SpawnedAnonymous($behavior, $props)"
override def productPrefix = "SpawnedAnonymous"
override def _1: Behavior[T] = behavior
override def _2: Props = props
override def canEqual(o: Any) = o.isInstanceOf[SpawnedAnonymous[_]]
}
object SpawnedAnonymous {
def apply[T](behavior: Behavior[T], props: Props = Props.empty): SpawnedAnonymous[T] = new SpawnedAnonymous(behavior, props, null)
def unapply[T](s: SpawnedAnonymous[T]): Option[(Behavior[T], Props)] = Some((s.behavior, s.props))
}
def spawned[T](behavior: Behavior[T], childName: String, ref: ActorRef[T]): Spawned[T] = new Spawned(behavior, childName, Props.empty, ref)
/**
* The behavior spawned a named adapter, through `ctx.spawnMessageAdapter`
* The behavior spawned a named child with the given behavior and specific props
*/
final class SpawnedAdapter[T](val name: String, val ref: ActorRef[T])
extends Effect with Product1[String] with Serializable {
override def equals(other: Any) = other match {
case o: SpawnedAdapter[_] this.name == o.name
case _ false
}
override def hashCode: Int = name.##
override def toString: String = s"SpawnedAdapter($name)"
override def productPrefix = "SpawnedAdapter"
override def _1: String = name
override def canEqual(o: Any) = o.isInstanceOf[SpawnedAdapter[_]]
}
object SpawnedAdapter {
def apply[T](name: String): SpawnedAdapter[T] = new SpawnedAdapter(name, null)
def unapply[T](s: SpawnedAdapter[T]): Option[Tuple1[String]] = Some(Tuple1(s.name))
}
def spawned[T](behavior: Behavior[T], childName: String, props: Props): Spawned[T] = Spawned(behavior, childName, props)
/**
* The behavior spawned an anonymous adapter, through `ctx.spawnMessageAdapter`
* The behavior spawned a named child with the given behavior and specific props
*/
final class SpawnedAnonymousAdapter[T](val ref: ActorRef[T])
extends Effect with Product with Serializable {
override def equals(other: Any) = other match {
case o: SpawnedAnonymousAdapter[_] true
case _ false
}
override def hashCode: Int = Nil.##
override def toString: String = "SpawnedAnonymousAdapter"
override def productPrefix = "SpawnedAnonymousAdapter"
override def productIterator = Iterator.empty
override def productArity = 0
override def productElement(n: Int) = throw new NoSuchElementException
override def canEqual(o: Any) = o.isInstanceOf[SpawnedAnonymousAdapter[_]]
}
object SpawnedAnonymousAdapter {
def apply[T]() = new SpawnedAnonymousAdapter[T](null)
def unapply[T](s: SpawnedAnonymousAdapter[T]): Boolean = true
}
def spawned[T](behavior: Behavior[T], childName: String, props: Props, ref: ActorRef[T]): Spawned[T] = new Spawned(behavior, childName, props, ref)
/**
* The behavior spawned an anonymous child with the given behavior with no specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T]): SpawnedAnonymous[T] = SpawnedAnonymous(behavior)
/**
* The behavior spawned an anonymous child with the given behavior with no specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T], ref: ActorRef[T]): SpawnedAnonymous[T] = new SpawnedAnonymous(behavior, Props.empty, ref)
/**
* The behavior spawned an anonymous child with the given behavior with specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T], props: Props): SpawnedAnonymous[T] = SpawnedAnonymous(behavior, props)
/**
* The behavior spawned an anonymous child with the given behavior with specific props
*/
def spawnedAnonymous[T](behavior: Behavior[T], props: Props, ref: ActorRef[T]): SpawnedAnonymous[T] = new SpawnedAnonymous(behavior, props, ref)
/**
* The behavior stopped `childName`
*/
final case class Stopped(childName: String) extends Effect
def stopped(childName: String): Stopped = Stopped(childName)
/**
* The behavior started watching `other`, through `ctx.watch(other)`
*/
final case class Watched[T](other: ActorRef[T]) extends Effect
def watched[T](other: ActorRef[T]): Watched[T] = Watched(other)
/**
* The behavior started watching `other`, through `ctx.unwatch(other)`
*/
final case class Unwatched[T](other: ActorRef[T]) extends Effect
def unwatched[T](other: ActorRef[T]): Unwatched[T] = Unwatched(other)
/**
* The behavior set a new receive timeout, with `msg` as timeout notification
*/
final case class ReceiveTimeoutSet[T](d: Duration, msg: T) extends Effect
def receiveTimeoutSet[T](d: FiniteDuration, msg: T): ReceiveTimeoutSet[T] = ReceiveTimeoutSet(d, msg)
/**
* The behavior used `ctx.schedule` to schedule `msg` to be sent to `target` after `delay`
* FIXME what about events scheduled through the scheduler?
*/
final case class Scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U) extends Effect
def scheduled[U](delay: FiniteDuration, target: ActorRef[U], msg: U): Scheduled[U] =
Scheduled(delay, target, msg)
/**
* Used to represent an empty list of effects - in other words, the behavior didn't do anything observable
*/
case object NoEffects extends NoEffects
def noEffects(): NoEffects = NoEffects
/**
* Used for NoEffects expectations by type
*/
sealed trait NoEffects extends Effect
}

View file

@ -16,9 +16,6 @@ import java.util.concurrent.TimeUnit;
import static akka.Done.done;
import static org.junit.Assert.assertEquals;
/**
* Copyright (C) 2009-2018 Lightbend Inc. <http://www.lightbend.com>
*/
public class ActorTestKitTest extends JUnitSuite {
@ClassRule

View file

@ -0,0 +1,298 @@
/*
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.actor.testkit.typed.javadsl;
import akka.Done;
import akka.actor.testkit.typed.Effect;
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.Props;
import akka.actor.typed.javadsl.Behaviors;
import org.junit.Ignore;
import org.junit.Test;
import org.scalatest.junit.JUnitSuite;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class BehaviorTestKitTest extends JUnitSuite {
public interface Command {
}
public static class SpawnWatchAndUnWatch implements Command {
private final String name;
public SpawnWatchAndUnWatch(String name) {
this.name = name;
}
}
public static class SpawnAndWatchWith implements Command {
private final String name;
public SpawnAndWatchWith(String name) {
this.name = name;
}
}
public static class SpawnSession implements Command {
private final ActorRef<ActorRef<String>> replyTo;
private final ActorRef<String> sessionHandler;
public SpawnSession(ActorRef<ActorRef<String>> replyTo, ActorRef<String> sessionHandler) {
this.replyTo = replyTo;
this.sessionHandler = sessionHandler;
}
}
public static class KillSession implements Command {
private final ActorRef<String> session;
private final ActorRef<Done> replyTo;
public KillSession(ActorRef<String> session, ActorRef<Done> replyTo) {
this.session = session;
this.replyTo = replyTo;
}
}
public static class CreateMessageAdapter implements Command {
private final Class<Object> clazz;
private final Function<Object, Command> f;
public CreateMessageAdapter(Class clazz, Function<Object, Command> f) {
this.clazz = clazz;
this.f = f;
}
}
public static class SpawnChildren implements Command {
private final int numberOfChildren;
public SpawnChildren(int numberOfChildren) {
this.numberOfChildren = numberOfChildren;
}
}
public static class SpawnChildrenAnonymous implements Command {
private final int numberOfChildren;
public SpawnChildrenAnonymous(int numberOfChildren) {
this.numberOfChildren = numberOfChildren;
}
}
public static class SpawnChildrenWithProps implements Command {
private final int numberOfChildren;
private final Props props;
public SpawnChildrenWithProps(int numberOfChildren, Props props) {
this.numberOfChildren = numberOfChildren;
this.props = props;
}
}
public static class SpawnChildrenAnonymousWithProps implements Command {
private final int numberOfChildren;
private final Props props;
public SpawnChildrenAnonymousWithProps(int numberOfChildren, Props props) {
this.numberOfChildren = numberOfChildren;
this.props = props;
}
}
public interface Action {
}
private static Behavior<Action> childInitial = Behaviors.ignore();
private static Props props = Props.empty().withDispatcherFromConfig("cat");
private static Behavior<Command> behavior = Behaviors.receive(Command.class)
.onMessage(SpawnChildren.class, (ctx, msg) -> {
IntStream.range(0, msg.numberOfChildren).forEach(i -> {
ctx.spawn(childInitial, "child" + i);
});
return Behaviors.same();
})
.onMessage(SpawnChildrenAnonymous.class, (ctx, msg) -> {
IntStream.range(0, msg.numberOfChildren).forEach(i -> {
ctx.spawnAnonymous(childInitial);
});
return Behaviors.same();
})
.onMessage(SpawnChildrenWithProps.class, (ctx, msg) -> {
IntStream.range(0, msg.numberOfChildren).forEach(i -> {
ctx.spawn(childInitial, "child" + i, msg.props);
});
return Behaviors.same();
})
.onMessage(SpawnChildrenAnonymousWithProps.class, (ctx, msg) -> {
IntStream.range(0, msg.numberOfChildren).forEach(i -> {
ctx.spawnAnonymous(childInitial, msg.props);
});
return Behaviors.same();
})
.onMessage(CreateMessageAdapter.class, (ctx, msg) -> {
ctx.messageAdapter(msg.clazz, msg.f);
return Behaviors.same();
})
.onMessage(SpawnWatchAndUnWatch.class, (ctx, msg) -> {
ActorRef<Action> c = ctx.spawn(childInitial, msg.name);
ctx.watch(c);
ctx.unwatch(c);
return Behaviors.same();
})
.onMessage(SpawnAndWatchWith.class, (ctx, msg) -> {
ActorRef<Action> c = ctx.spawn(childInitial, msg.name);
ctx.watchWith(c, msg);
return Behaviors.same();
})
.onMessage(SpawnSession.class, (ctx, msg) -> {
ActorRef<String> session = ctx.spawnAnonymous(Behaviors.receiveMessage( m -> {
msg.sessionHandler.tell(m);
return Behaviors.same();
}));
msg.replyTo.tell(session);
return Behaviors.same();
})
.onMessage(KillSession.class, (ctx, msg) -> {
ctx.stop(msg.session);
msg.replyTo.tell(Done.getInstance());
return Behaviors.same();
})
.build();
@Test
public void allowAssertionsOnEffectType() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.run(new SpawnChildren(1));
Effect.Spawned spawned = test.expectEffectClass(Effect.Spawned.class);
assertEquals(spawned.childName(), "child0");
}
@Test
public void allowExpectingNoEffects() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.expectEffect(Effects.noEffects());
}
@Test
public void allowsExpectingNoEffectByType() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.expectEffectClass(Effect.NoEffects.class);
}
@Test
public void returnEffectsThatHaveTakenPlace() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
assertFalse(test.hasEffects());
test.run(new SpawnChildrenAnonymous(1));
assertTrue(test.hasEffects());
}
@Test
@Ignore("Not supported for Java API")
public void allowAssertionsUsingPartialFunctions() {
}
@Test
public void spawnChildrenWithNoProps() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.run(new SpawnChildren(2));
List<Effect> allEffects = test.getAllEffects();
assertEquals(
Arrays.asList(Effects.spawned(childInitial, "child0"), Effects.spawned(childInitial, "child1", Props.empty())),
allEffects
);
}
@Test
public void spawnChildrenWithProps() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.run(new SpawnChildrenWithProps(1, props));
assertEquals(props, test.expectEffectClass(Effect.Spawned.class).props());
}
@Test
public void spawnAnonChildrenWithNoProps() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.run(new SpawnChildrenAnonymous(2));
List<Effect> allEffects = test.getAllEffects();
assertEquals(
Arrays.asList(Effects.spawnedAnonymous(childInitial), Effects.spawnedAnonymous(childInitial, Props.empty())),
allEffects
);
}
@Test
public void spawnAnonChildrenWithProps() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.run(new SpawnChildrenAnonymousWithProps(1, props));
assertEquals(props, test.expectEffectClass(Effect.SpawnedAnonymous.class).props());
}
@Test
public void createMessageAdapters() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
SpawnChildren adaptedMessage = new SpawnChildren(1);
test.run(new CreateMessageAdapter(String.class, o -> adaptedMessage));
Effect.MessageAdapter mAdapter = test.expectEffectClass(Effect.MessageAdapter.class);
assertEquals(String.class, mAdapter.messageClass());
assertEquals(adaptedMessage, mAdapter.adaptFunction().apply("anything"));
}
@Test
public void recordWatching() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.run(new SpawnWatchAndUnWatch("name"));
ActorRef<Object> child = test.childInbox("name").getRef();
test.expectEffectClass(Effect.Spawned.class);
assertEquals(child, test.expectEffectClass(Effect.Watched.class).other());
assertEquals(child, test.expectEffectClass(Effect.Unwatched.class).other());
}
@Test
public void recordWatchWith() {
BehaviorTestKit<Command> test = BehaviorTestKit.create(behavior);
test.run(new SpawnWatchAndUnWatch("name"));
ActorRef<Object> child = test.childInbox("name").getRef();
test.expectEffectClass(Effect.Spawned.class);
assertEquals(child, test.expectEffectClass(Effect.Watched.class).other());
}
@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());
Effect.SpawnedAnonymous s = test.expectEffectClass(Effect.SpawnedAnonymous.class);
assertEquals(sessionRef, s.ref());
BehaviorTestKit<String> session = test.childTestKit(sessionRef);
session.run("hello");
assertEquals(Collections.singletonList("hello"), h.getAllReceived());
TestInbox<Done> d = TestInbox.create();
test.run(new KillSession(sessionRef, d.getRef()));
assertEquals(Collections.singletonList(Done.getInstance()), d.getAllReceived());
test.expectEffectClass(Effect.Stopped.class);
}
}

View file

@ -7,11 +7,14 @@ package akka.actor.testkit.typed.scaladsl
import akka.Done
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{ ActorRef, Behavior, Props }
import akka.actor.testkit.typed.scaladsl.Effects.{ NoEffects, Spawned, SpawnedAdapter, SpawnedAnonymous, SpawnedAnonymousAdapter, Watched, Unwatched }
import akka.actor.testkit.typed.Effect
import akka.actor.testkit.typed.Effect._
import akka.actor.testkit.typed.scaladsl.BehaviorTestKitSpec.{ Child, Father }
import akka.actor.testkit.typed.scaladsl.BehaviorTestKitSpec.Father._
import org.scalatest.{ Matchers, WordSpec }
import scala.reflect.ClassTag
object BehaviorTestKitSpec {
object Father {
@ -25,6 +28,7 @@ object BehaviorTestKitSpec {
case class SpawnAnonymousWithProps(numberOfChildren: Int, props: Props) extends Command
case object SpawnAdapter extends Command
case class SpawnAdapterWithName(name: String) extends Command
case class CreateMessageAdapter[U](messageClass: Class[U], f: U Command) extends Command
case class SpawnAndWatchUnwatch(name: String) extends Command
case class SpawnAndWatchWith(name: String) extends Command
case class SpawnSession(replyTo: ActorRef[ActorRef[String]], sessionHandler: ActorRef[String]) extends Command
@ -82,6 +86,10 @@ object BehaviorTestKitSpec {
ctx.stop(session)
replyTo ! Done
Behaviors.same
case CreateMessageAdapter(messageClass, f)
ctx.messageAdapter(f)(ClassTag(messageClass))
Behaviors.same
}
}
}
@ -103,14 +111,14 @@ object BehaviorTestKitSpec {
class BehaviorTestKitSpec extends WordSpec with Matchers {
private val props = Props.empty
private val props = Props.empty.withDispatcherFromConfig("cat")
"BehaviorTestKit" must {
"allow assertions on effect type" in {
val testkit = BehaviorTestKit[Father.Command](Father.init)
testkit.run(SpawnAnonymous(1))
val spawnAnonymous = testkit.expectEffectType[Effects.SpawnedAnonymous[_]]
val spawnAnonymous = testkit.expectEffectType[Effect.SpawnedAnonymous[_]]
spawnAnonymous.props should ===(Props.empty)
}
@ -209,6 +217,14 @@ class BehaviorTestKitSpec extends WordSpec with Matchers {
}
}
"BehaviorTestkit's messageAdapter" must {
"create message adapters and record effects" in {
val testkit = BehaviorTestKit[Father.Command](Father.init)
testkit.run(CreateMessageAdapter(classOf[String], (_: String) SpawnChildren(1)))
testkit.expectEffectType[MessageAdapter[String, Command]]
}
}
"BehaviorTestkit's run" can {
"run behaviors with messages without canonicalization" in {
val testkit = BehaviorTestKit[Father.Command](Father.init)
@ -224,9 +240,9 @@ class BehaviorTestKitSpec extends WordSpec with Matchers {
testkit.run(SpawnAndWatchUnwatch("hello"))
val child = testkit.childInbox("hello").ref
testkit.retrieveAllEffects() should be(Seq(
Spawned(Child.initial, "hello", Props.empty),
Watched(child),
Unwatched(child)
Effects.spawned(Child.initial, "hello", Props.empty),
Effects.watched(child),
Effects.unwatched(child)
))
}
@ -235,8 +251,8 @@ class BehaviorTestKitSpec extends WordSpec with Matchers {
testkit.run(SpawnAndWatchWith("hello"))
val child = testkit.childInbox("hello").ref
testkit.retrieveAllEffects() should be(Seq(
Spawned(Child.initial, "hello", Props.empty),
Watched(child)
Effects.spawned(Child.initial, "hello", Props.empty),
Effects.watched(child)
))
}
}
@ -250,7 +266,7 @@ class BehaviorTestKitSpec extends WordSpec with Matchers {
val sessionRef = i.receiveMessage()
i.hasMessages shouldBe false
val (s: SpawnedAnonymous[_]) :: Nil = testkit.retrieveAllEffects()
val s = testkit.expectEffectType[SpawnedAnonymous[_]]
// must be able to get the created ref, even without explicit reply
s.ref shouldBe sessionRef
@ -262,6 +278,7 @@ class BehaviorTestKitSpec extends WordSpec with Matchers {
testkit.run(KillSession(sessionRef, d.ref))
d.receiveAll shouldBe Seq(Done)
testkit.expectEffectType[Stopped]
}
}
}

View file

@ -7,7 +7,7 @@ package akka.actor.testkit.typed.scaladsl
//#imports
import akka.actor.typed._
import akka.actor.typed.scaladsl._
import akka.actor.testkit.typed.scaladsl.Effects._
import akka.actor.testkit.typed.Effect._
//#imports
import org.scalatest.{ Matchers, WordSpec }