如何在单例对象上注入依赖项?

时间:2013-06-20 18:44:11

标签: unit-testing scala dependency-injection playframework-2.0 scalatest

假设我正在测试一个方法,该方法依赖于一个名为WS(Web Service)的(导入的)单例实例,该实例有一个方法 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]),但WS的属性和方法在绑定时会丢失单例到一个需要Class [WS.type]的val。这意味着我不能简单地使用简单的蛋糕模式,如下所示:

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

object ApplicationContext extends WSComponent {
  val ws = WS
}

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

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

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

有哪些方法可以在单例对象上注入依赖项?我喜欢使用DI的蛋糕模式,但它为我的代码引入了相当多的样板,所以理想的解决方案不会使代码更加复杂。

提前感谢您提出任何建议。

2 个答案:

答案 0 :(得分:1)

Singleton对象确实有类型,你可以调用它们的方法:

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

无法编译,确实Class[...]也没有url()方法。您只需输入wsWS.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]
}

答案 1 :(得分:0)

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

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

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

然后在一个特性中使用它,混合成需要它的类:

trait WSClient{
  val ws:WSMethods
}

一旦你这样做,它就更具有可模仿性。这有点麻烦,但当对象没有混合定义其操作的特征时,就会出现这种情况。如果类型安全的人用WS完成了那个,那么模拟会更容易。此外,大多数模拟框架(可能全部)如果你尝试一些东西将会barf:

val m = mock[WS.type]