使用 Akka 路由器测试 Spray API
Testing Spray API with Akka Router
我正在开发一个 Spray API,使用 Akka 路由器将传入消息发送到 actors 池以处理逻辑。现在我想为 API 编写一些测试,但我正在努力寻找代码的正确结构。 API 目前看起来如下:
import akka.actor.{ActorRef, ActorSystem, Props, Actor}
import akka.io.IO
import akka.routing.SmallestMailboxPool
import akka.util.Timeout
import akka.pattern.ask
import com.typesafe.config.ConfigFactory
import spray.json._
import spray.can.Http
import scala.concurrent.duration._
import spray.routing._
import spray.http._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import scala.util.Failure
object implicits{
implicit val system = ActorSystem("ApiSystem")
implicit val timeout = Timeout(5.seconds)
implicit val conf = ConfigFactory.load()
// Custom case class for parsing JSON parameter.
case class Msg(key1:String, key2:String, key3:Int)
object JsonProtocol extends DefaultJsonProtocol {
implicit val msg = jsonFormat3(Msg)
}
case class PostMsg(msg:String)
case object PostSuccess
case class PostFailure(msg:String)
}
import implicits._
object MyApi extends App {
override def main(Args: Array[String]):Unit = {
// create and start our service actor
val service = system.actorOf(Props(new MyApiActor(system)), "MyApi-service")
IO(Http) ? Http.Bind(service, interface = conf.getString("http.host"), port = conf.getInt("http.port"))
}
}
class MyApiActor(system: ActorSystem) extends Actor with MyApiService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute)
}
// this trait defines our service behavior independently from the service actor
trait MyApiService extends HttpService {
import implicits.JsonProtocol._
var actorPool = system.actorOf(SmallestMailboxPool(conf.getInt("actor-number")).props(Props(new HandlingActor(conf))), "msgRouter")
val myRoute =
path("msg") {
post {
entity(as[String]) { obj =>
try{
// if this parsing succeeds, the posted msg satisfies the preconditions set.
obj.parseJson.convertTo[Msg]
} catch {
case e: DeserializationException => {
complete(HttpResponse(status=StatusCodes.BadRequest, entity="Invalid json provided."))
}
case e: Exception => {
complete(HttpResponse(status=StatusCodes.InternalServerError, entity="Unknown internal server error."))
}
}
onComplete(actorPool ? PostMsg(obj)) {
case Success(value) => complete(HttpResponse(status = StatusCodes.OK, entity = "Pushed Msg"))
case Failure(value) => complete(HttpResponse(status = StatusCodes.InternalServerError, entity = "Handling failed."))
}
}
}
}
}
我想测试的是 API 对各种 HTTP 消息的响应(即正确调用、错误调用等)。处理 actor 中的逻辑只是将消息推送到 Kafka 总线,所以我想 "mock" 这种行为(即如果推送成功,能够测试 API 响应以及会发生什么当这次推送失败时)。
我目前最头疼的事情是如何设置测试。现在,我使用与显示的主要方法中相同的命令设置 API,但我需要指定一个不同的 actorPool,因为我不希望实际推送任何消息。我应该如何最好地完成此类测试?
我正在使用带有 Akka 和 Spray 测试套件的 Scalatest。 (如有必要,还可能加上 mockito 用于模拟)
我有一些建议可以让您的测试更轻松:
不要在你的特征中创建演员池。而是在路由中使用 def
而不是 val
从 ActorPool 中注入 ActorRef
。然后注入你的 actorPool TestProbe()
来测试会更容易。例如(我没有tried/compiled这段代码):
class MyApiActor extends Actor with MyApiService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
val actorPool = context.actorOf(SmallestMailboxPool(conf.getInt("actor-number")).props(Props(new HandlingActor(conf))), "msgRouter")
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute(actorPool))
}
// this trait defines our service behavior independently from the service actor
trait MyApiService extends HttpService {
import implicits.JsonProtocol._
def myRoute(actorPool: ActorRef) =
path("msg") {
post {
entity(as[String]) { obj =>
try{
// if this parsing succeeds, the posted msg satisfies the preconditions set.
obj.parseJson.convertTo[Msg]
} catch {
case e: DeserializationException => {
complete(StatusCodes.BadRequest, "Invalid json provided.")
}
case e: Exception => {
complete(StatusCodes.InternalServerError, "Unknown internal server error.")
}
}
onComplete(actorPool ? PostMsg(obj)) {
case Success(value) => complete(StatusCodes.OK, "Pushed Msg")
case Failure(value) => complete(StatusCodes.InternalServerError, "Handling failed.")
}
}
}
}
}
那么测试可以是这样的:
class HttpListenerSpec extends WordSpecLike with Matchers with ScalatestRouteTest with MyApiService {
"An HttpListener" should {
"accept GET at /msg" in {
val actorPool = TestProbe()
(stuff for responding with TestProbe()...)
Get("/msg") ~> myRoute(actorPool.ref) ~> check {
status shouldBe OK
val response = responseAs[String]
assert(...)
}
}
}
}
另外,作为最后的建议。有集成 spray json 和 spray 的隐式转换,所以你可以做 entity(as[Msg])
。查找以下内容:
import spray.httpx.marshalling._
import spray.httpx.unmarshalling._
import spray.httpx.SprayJsonSupport._
import MsgJsonProtocol._
我正在开发一个 Spray API,使用 Akka 路由器将传入消息发送到 actors 池以处理逻辑。现在我想为 API 编写一些测试,但我正在努力寻找代码的正确结构。 API 目前看起来如下:
import akka.actor.{ActorRef, ActorSystem, Props, Actor}
import akka.io.IO
import akka.routing.SmallestMailboxPool
import akka.util.Timeout
import akka.pattern.ask
import com.typesafe.config.ConfigFactory
import spray.json._
import spray.can.Http
import scala.concurrent.duration._
import spray.routing._
import spray.http._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import scala.util.Failure
object implicits{
implicit val system = ActorSystem("ApiSystem")
implicit val timeout = Timeout(5.seconds)
implicit val conf = ConfigFactory.load()
// Custom case class for parsing JSON parameter.
case class Msg(key1:String, key2:String, key3:Int)
object JsonProtocol extends DefaultJsonProtocol {
implicit val msg = jsonFormat3(Msg)
}
case class PostMsg(msg:String)
case object PostSuccess
case class PostFailure(msg:String)
}
import implicits._
object MyApi extends App {
override def main(Args: Array[String]):Unit = {
// create and start our service actor
val service = system.actorOf(Props(new MyApiActor(system)), "MyApi-service")
IO(Http) ? Http.Bind(service, interface = conf.getString("http.host"), port = conf.getInt("http.port"))
}
}
class MyApiActor(system: ActorSystem) extends Actor with MyApiService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute)
}
// this trait defines our service behavior independently from the service actor
trait MyApiService extends HttpService {
import implicits.JsonProtocol._
var actorPool = system.actorOf(SmallestMailboxPool(conf.getInt("actor-number")).props(Props(new HandlingActor(conf))), "msgRouter")
val myRoute =
path("msg") {
post {
entity(as[String]) { obj =>
try{
// if this parsing succeeds, the posted msg satisfies the preconditions set.
obj.parseJson.convertTo[Msg]
} catch {
case e: DeserializationException => {
complete(HttpResponse(status=StatusCodes.BadRequest, entity="Invalid json provided."))
}
case e: Exception => {
complete(HttpResponse(status=StatusCodes.InternalServerError, entity="Unknown internal server error."))
}
}
onComplete(actorPool ? PostMsg(obj)) {
case Success(value) => complete(HttpResponse(status = StatusCodes.OK, entity = "Pushed Msg"))
case Failure(value) => complete(HttpResponse(status = StatusCodes.InternalServerError, entity = "Handling failed."))
}
}
}
}
}
我想测试的是 API 对各种 HTTP 消息的响应(即正确调用、错误调用等)。处理 actor 中的逻辑只是将消息推送到 Kafka 总线,所以我想 "mock" 这种行为(即如果推送成功,能够测试 API 响应以及会发生什么当这次推送失败时)。
我目前最头疼的事情是如何设置测试。现在,我使用与显示的主要方法中相同的命令设置 API,但我需要指定一个不同的 actorPool,因为我不希望实际推送任何消息。我应该如何最好地完成此类测试?
我正在使用带有 Akka 和 Spray 测试套件的 Scalatest。 (如有必要,还可能加上 mockito 用于模拟)
我有一些建议可以让您的测试更轻松:
不要在你的特征中创建演员池。而是在路由中使用 def
而不是 val
从 ActorPool 中注入 ActorRef
。然后注入你的 actorPool TestProbe()
来测试会更容易。例如(我没有tried/compiled这段代码):
class MyApiActor extends Actor with MyApiService {
// the HttpService trait defines only one abstract member, which
// connects the services environment to the enclosing actor or test
def actorRefFactory = context
val actorPool = context.actorOf(SmallestMailboxPool(conf.getInt("actor-number")).props(Props(new HandlingActor(conf))), "msgRouter")
// this actor only runs our route, but you could add
// other things here, like request stream processing
// or timeout handling
def receive = runRoute(myRoute(actorPool))
}
// this trait defines our service behavior independently from the service actor
trait MyApiService extends HttpService {
import implicits.JsonProtocol._
def myRoute(actorPool: ActorRef) =
path("msg") {
post {
entity(as[String]) { obj =>
try{
// if this parsing succeeds, the posted msg satisfies the preconditions set.
obj.parseJson.convertTo[Msg]
} catch {
case e: DeserializationException => {
complete(StatusCodes.BadRequest, "Invalid json provided.")
}
case e: Exception => {
complete(StatusCodes.InternalServerError, "Unknown internal server error.")
}
}
onComplete(actorPool ? PostMsg(obj)) {
case Success(value) => complete(StatusCodes.OK, "Pushed Msg")
case Failure(value) => complete(StatusCodes.InternalServerError, "Handling failed.")
}
}
}
}
}
那么测试可以是这样的:
class HttpListenerSpec extends WordSpecLike with Matchers with ScalatestRouteTest with MyApiService {
"An HttpListener" should {
"accept GET at /msg" in {
val actorPool = TestProbe()
(stuff for responding with TestProbe()...)
Get("/msg") ~> myRoute(actorPool.ref) ~> check {
status shouldBe OK
val response = responseAs[String]
assert(...)
}
}
}
}
另外,作为最后的建议。有集成 spray json 和 spray 的隐式转换,所以你可以做 entity(as[Msg])
。查找以下内容:
import spray.httpx.marshalling._
import spray.httpx.unmarshalling._
import spray.httpx.SprayJsonSupport._
import MsgJsonProtocol._