Внедрение фиктивных актеров в маршрут Spray для тестирования

Несколько групп в моем отделе начали использовать Spray для разработки веб-сервисов на основе REST, и все они столкнулись с похожей проблемой, и до сих пор не было хороших решений.

Предположим, у вас было следующее:

FooService extends Actor { ??? }

а потом в другом месте:

path("SomePath") {
  id =>
    get {
      requestContext =>
        // I apologize for the janky Props usage here, just an example
        val fooService = actorRefFactory.actorOf(Props(new FooService(requestContext))) 
        queryService ! SomeMessage(id)
    }
}

Другими словами, у каждой конечной точки есть соответствующий актор, и внутри маршрута актор этого типа будет запущен с контекстом запроса, ему будет передано сообщение, и этот актор обработает HttpResponse & stop.

У меня всегда были достаточно простые деревья маршрутов, которые я тестировал только самих Актеров и позволял тестированию маршрутов обрабатываться интеграционными тестами, но здесь меня отклонили. Итак, проблема в том, что для модульных тестов люди хотят иметь возможность заменить FooService на MockFooService.

Есть ли стандартный способ справиться с этой ситуацией?


person geoffjentry    schedule 18.03.2015    source источник


Ответы (1)


Я бы выбрал шаблон торта, в котором вы можете добавить реализацию в последний момент:

trait MyService extends HttpService with FooService {
  val route =
    path("SomePath") { id =>
        get { requestContext =>
            val fooService = actorRefFactory.actorOf(fooProps(requestContext)) 
            queryService ! SomeMessage(id)
        }
    }
}

trait FooService {
  def fooProps(requestContext: RequestContext): Props
}

trait TestFooService extends FooService {
  def fooProps(requestContext: RequestContext) =
    Props(new TestFooService(requestContext))
}

trait ProdFooService extends FooService {
  def fooProps(requestContext: RequestContext) =
    Props(new FooService(requestContext))
}

trait MyTestService extends MyService with TestFooService

trait MyProdService extends MyService with ProdFooService

Я написал его в текстовом редакторе, поэтому не уверен, что он скомпилируется.

Если вы хотите провести тестирование вообще без актеров, вы можете извлечь эти две строки:

val fooService = actorRefFactory.actorOf(fooProps(requestContext)) 
queryService ! SomeMessage(id)

в какой-то метод и спрятать за этим актера. Например:

def processRequest[T](msg: T): Unit = {
  // those 2 lines, maybe pass other args here too like context
}

Этот метод можно переопределить таким же образом, как в шаблоне торта, а для тестов можно вообще не использовать актеров.

person yǝsʞǝla    schedule 18.03.2015
comment
Это хороший способ сделать это. Лучше всего скрыть актеров, если это возможно. - person Oliver Shaw; 18.03.2015
comment
Так что это довольно близко к тому, что я пытался сделать, и немного более элегантно, таким образом, который решает, как я пытался что-то сделать. По какой-то причине я в конечном итоге разделил функцию создания реквизита на отдельное дерево свойств — вспоминая, что сначала я пытался сделать что-то необычное, но продолжал сталкиваться с проблемами стирания типов, поэтому к тому моменту я потерял лес для деревьев. . Спасибо, позабочусь, чтобы я не упустил какую-то тонкость в нашей базовой задаче (у некоторых есть другие сложности, но c'est la vie) - person geoffjentry; 18.03.2015
comment
оказывается, то, что я сказал, было правдой, и что моя попытка решить шутки привела к слишком сложному решению, когда все это время я хотел это. Было несколько исключительных случаев, но я решил это с помощью FP. Все в порядке в мире. Спасибо. - person geoffjentry; 19.03.2015