1

假设我正在测试一个方法,它依赖于一个名为 WS(Web 服务)的(导入的)单例实例,它有一个方法url(url: String),它接受一个 URL 并返回一个请求。

def doRequest(url: String): Future[Response] = {
  val request = WS.url(url)
  for {
    response <- request.post(params)
  } yield {
    val res: JsResult[MyResult] = response.json.validate[MyResult]
    res.getOrElse(throw new NotSupportedException)
  }
}

我希望能够注入 WS 依赖项,这样我的单元测试就不需要实际的出站 http 请求,而是可以依赖于模拟 WS 实例。

这对我来说是一个挑战,因为虽然单例在技术上确实有一个类型 (Class[WS.type]),但是当将单例绑定到需要 Class[WS.type] 的 val 时,WS 的属性和方法会丢失。这意味着我不能简单地使用简单的蛋糕图案,如下所示:

trait WSComponent {
  val ws: Class[_ <: WS.type]
}

object ApplicationContext extends WSComponent {
  val ws = WS
}

object TestContext extends WSComponent {
  val ws = mock[WS]
}

如果我这样做,然后在任一上下文中调用 WS 的方法,我会得到一个编译错误,即 Class[_ <: WS.type] 没有调用(例如)url() 的方法。

出于类似的原因(基本上,单例对象没有类型——即使它们有——),我不能提供一个采用 WS.type 的隐式参数,因为我将再次丢失方法和在单例对象上声明的属性。

有哪些方法可以注入对单例对象的依赖项?我喜欢为 DI 使用 cake 模式,但它在我的代码中引入了相当多的样板,因此理想的解决方案不会将代码与过多的样板混合在一起。

提前感谢您的任何建议。

4

2 回答 2

1

单例对象确实有类型,您可以在它们上调用方法:

scala> val i: Int.type = Int
i: Int.type = object scala.Int

scala> i.box(42)
res0: Integer = 42

我猜你的错误与

val ws: Class[_ <: WS.type]

正在实施:

val ws = WS

那无法编译,实际上 aClass[...]也没有url()方法。ws您只需键入WS.type

trait WSComponent {
  val ws: WS.type
}

并将嘲笑改为mock[WS.type].


编辑:下面的另一种方式只有在你可以控制WS类型的情况下才有效(显然这里不是这种情况,因为它来自游戏)

如果你真的想避免单例类型,你可以WS变成一个带有单例实现的特征,并且只在你的蛋糕中引用这个特征。

trait WS {
  def url(url: String): Request
}

object SingletonWS extends WS {
  def url(url: String) = ??? // actual implementation
}

在你的蛋糕里:

trait WSComponent {
  val ws: WS
}

object ApplicationContext extends WSComponent {
  val ws = SingletonWS
}

object TestContext extends WSComponent {
  val ws = mock[WS]
}
于 2013-06-20T19:19:52.043 回答
0

您可以尝试定义一个特征,其中包含WS您使用的调用,然后定义一个简单的包装器 impl 委托给WS. 像这样的东西:

trait WSMethods{
  def url(str:String):Request
}

object WSWrapper extends WSMethods{
  def url(str:String) = WS.url(str)
}

然后像这样在一个特征中使用它,你可以将它混合到需要它的类中:

trait WSClient{
  val ws:WSMethods
}

一旦你这样做,它就更可笑了。这有点麻烦,但这就是当对象不混合定义其操作的特征时的方式。如果 typesafe 的人已经这样做了,WS那么模拟会更容易。此外,如果您尝试以下方式,大多数模拟框架(可能全部)都会失败:

val m = mock[WS.type]
于 2013-06-20T19:36:18.613 回答