2016-02-12 131 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