2016-02-12 50 views
1

我正在使用Spray API,使用Akka路由器将传入消息发送到处理逻辑的actor池。现在我想为API编写一些测试,但我正在努力为代码找到正确的结构。该API看起来此刻如下:使用Akka路由器测试Spray 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消息(即正确的电话,不正确的电话等)的响应。处理参与者的逻辑仅仅是将消息推送到Kafka总线,所以我想“嘲笑”这种行为(即,如果此推送成功并能够在推送失败时测试API响应)。

我现在最挣扎的事情是如何设置测试。现在,我使用与所示主要方法中相同的命令来设置API,但我需要指定不同的actorPool,因为我不希望任何消息被实际推送。我应该如何最好地去实现这样的测试?

我正在使用Scalatest,使用Akka和Spray测试工具包。 (加上可能为的Mockito如果必要的话嘲讽)

+0

喷雾测试套件可让您直接测试您的路线,而无需启动演员系统。看起来你必须这样做,所以你可以使用Akka测试包来控制这些角色并拥有测试角色系统。 也许从这里的一些例子将有助于路线测试和特质组成: https://github.com/izmailoff/Spray_Mongo_REST_service/blob/master/rest/src/test/scala/com/example/service/GetTweetSpec。斯卡拉 –

回答

3

我有几个建议,让您的测试更加简单:

不要建立在你的特质演员池。相反,在路由中使用def而不是val从ActorPool注入ActorRef。那么注入你的actorPool TestProbe()来测试会更容易。例如(我还没试过/编译此代码):

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(...) 
     } 
    } 
    } 
} 

此外,作为最后的建议。有一些隐含的转换可以整合喷雾干燥剂和喷雾剂,所以你可以做entity(as[Msg])。寻找以下内容:

import spray.httpx.marshalling._ 
import spray.httpx.unmarshalling._ 
import spray.httpx.SprayJsonSupport._ 
import MsgJsonProtocol._ 
+0

太棒了!谢谢! :D对我而言,缺少的一步是将路线改为“def”而不是“val” – danielvdende