假设我正在测试一个方法,该方法依赖于一个名为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的蛋糕模式,但它为我的代码引入了相当多的样板,所以理想的解决方案不会使代码更加复杂。
提前感谢您提出任何建议。
答案 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()
方法。您只需输入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]
}
答案 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]