【问题标题】:How can I inject a dependency on a singleton object?如何注入对单例对象的依赖?
【发布时间】:2013-06-17 18:24:52
【问题描述】:

假设我正在测试一个方法,它依赖于一个名为 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]),但是在将单例绑定到 val 时,WS 的属性和方法会丢失它需要一个类 [WS.type]。这意味着我不能像这样简单地使用简单的蛋糕图案:

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>

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

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

提前感谢您的任何建议。

【问题讨论】:

  • 你怎么能有val ws: Class[_ &lt;: WS.type]和一个子类型val ws = WS?你不是说val ws: WS.type吗?

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


【解决方案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

那无法编译,而且实际上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]
}

【讨论】:

  • 我相信WS 来自游戏库的OP。它被定义为一个对象,并且不混合任何代表该对象公开的操作的特征。这种制造方式是模拟的死胡同,唯一的解决方案可能是包装,然后委托,然后在包装器上模拟。
  • cmbaxter 是正确的。 WS 确实来自游戏,它只扩展了 Class。尽管如此,gourlaysama 是正确的,如果我只使用 WS.type 而不是 Class[WS.type](或 Class[_ <: ws.type>
  • 哦,我没有看到与 play-framework 的链接。然后我想在任何地方使用WS.type 是唯一的选择。我猜mock[WS.type] 也可以。
  • 这将编译,但它不会工作。 mock 来自 Mockito,它不能模拟决赛。 scala 对象上的类型成员编译为最终类。
【解决方案2】:

您可以尝试定义一个特征,该特征包含您使用的来自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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-04
    • 2016-10-05
    • 2023-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多