scala:定义模拟对象的策略,使用implicits注入依赖项

ope*_*sas 0 testing dependency-injection scala mocking

我有一个Social对象,负责连接到Twitter,facebook等,并检索指定用户的提供者信息

对于每个提供者,我实现了单个TwitterAdapter,所有这些都继承自抽象类SocialAdapter

这是代码:https://github.com/RestOpenGov/ideas-ba/blob/master/webservice/app/services/security/SocialConnector.scala#L98

为了测试,我显然想要模拟TwitterAdapter,这样它就不会与twitter连接,而是返回一些固定的响应.

我发现的一个解决方案是使用隐式参数注入适配器列表.这个解决方案的问题是从其他函数调用Social.retrieveSocialProviderInfo,所以我必须通过所有调用链传递隐式List [SocialAdapter]参数,如下所示:

def createApplicationToken(accessToken: AccessToken)
  (implicit adapters: List[SocialAdapter] = Social.defaultAdapters)
: Either[List[Error], ApplicationToken] = {

  // go to social info provider and fetch information
  retrieveProviderInfo(accessToken).fold(
  [...]

def retrieveProviderInfo(accessToken: AccessToken)
  (implicit adapters: List[SocialAdapter] = Social.defaultAdapters)
: Either[List[Error], IdentityProviderInfo] = {
[...]
Run Code Online (Sandbox Code Playgroud)

最后

object Social {

  val defaultAdapters = List(TwitterAdapter, FacebookAdapter)

  def retrieveSocialProviderInfo
    (accessToken: AccessToken)
    (implicit adapters: List[SocialAdapter] = Social.defaultAdapters)   // adapters can be injected
  : Option[IdentityProviderInfo] = {
  [...]
Run Code Online (Sandbox Code Playgroud)

你明白了

它运行正常,通常我只是忽略第二组参数并从Social.defaultAdapters中选择默认值,我只在测试时将其设置为List(MockTwitterAdapter,MockFacebookAdapter),但我为了能够测试它而使代码混乱.

另一个解决方案是使Social.defaultAdapters成为var(而不是val),只需更改它以进行测试,通常在生产模式下它总是具有相同的值.

我认为这必须是一个非常常见的情况.是否有更好的策略来处理这些情况?或者也许某种方式来扩展隐式赋值的范围?或者我应该使用功能齐全的依赖注入框架?

Eri*_*ric 6

一个简单的方法就是一直使用特征:

// you can test this trait and override the adapters as you wish
// by overriding the defaultAdapters member
trait Social {

  implicit val defaultAdapters = List(TwitterAdapter, FacebookAdapter)

  def retrieveSocialProviderInfo(accessToken: AccessToken):
    Option[IdentityProviderInfo] = ...
}

// you can use this object directly in your production code
// if you don't want to mix it in
object Social extends Social

// or use the trait by mixing it with another
trait Application extends Social {
  def createApplicationToken(accessToken: AccessToken): 
    Either[List[Error], ApplicationToken] = {
    // the defaultAdapters are accessible to the 
    // retrieveProviderInfo method 
    retrieveProviderInfo(accessToken).fold(...)
  }
Run Code Online (Sandbox Code Playgroud)