【问题标题】:How to mock a Scala singleton object?如何模拟 Scala 单例对象?
【发布时间】:2014-08-13 18:41:16
【问题描述】:

我正在尝试模拟一个 Scala 单例对象。特别是,我需要模拟在服务组件(被测类)中使用的对象play.api.libs.ws.WS。 使用 Mockito 这是不可能的,测试执行会以下列方式失败:

[error]    MockitoException: : 
[error] Cannot mock/spy class play.api.libs.ws.WS$
[error] Mockito cannot mock/spy following:
[error]   - final classes
[error]   - anonymous classes
[error]   - primitive types  (GeolocationSpec.scala:18)

阅读here,Scalamock 似乎允许这样做:

要模拟一个独立的单例对象,请使用 org.scalamock.annotation.mockObject.

我的服务组件是这样的:

trait GeolocationService {
  def wsClient = WS
  def getPath(origin: Location, destination: Location): Future[Route]
}

class DefaultGeolocationService extends GeolocationService {

  val serviceProviderEndpoint = Play.current.configuration.getString("api.directions.endpoint")

  override def getPath(origin: Location, destination: Location): Future[Route] = {

    val params = Seq(
      "origin" -> s"${origin.lat},${origin.lon}",
      "destination" -> s"${destination.lat},${destination.lon}"
    );
    val resp = wsClient.url(serviceProviderEndpoint.get).withQueryString(params: _*).get()
    resp.map {
      // omitted code
    }
  }
}

我的 build.sbt 有所有这些依赖项:

[...]
"org.scalatest" %% "scalatest" % "2.2.1",
"org.specs2" %% "specs2" % "2.3.13" % "test",
"org.scalamock" %% "scalamock-specs2-support" % "3.0.1" % "test",
"org.scalamock" %% "scalamock-scalatest-support" % "3.0.1" % "test",
"org.scalamock" %% "scalamock" % "3.0.1",
[...]

但我找不到这个:org.scalamock.annotation.mockObject

也许这也可以使用 EasyMock 和 PowerMock 来完成,但我找不到任何 Scala 示例代码。

有什么想法吗?

【问题讨论】:

  • 模拟它实现的接口。将该接口作为使用它的依赖项。
  • 你怎么称呼它?喜欢mock [WS.type] 还是其他方式?
  • 我第二个@PolymorphicPotato。不幸的是,Play 的 WS 是一个对象,而不是通用特征的实现。如果您可以使用 WS 添加简短的代码 sn-p,我们可以指导您将其与 WS 解耦并使其更容易模拟。
  • @wheaties 是的,我发现使用 Mockito 模拟对象而没有编译错误的唯一方法是 mock[WS.type],但它不起作用。
  • @PolymorphicPotato 你能准确地告诉我你的意思吗?对于 WS 单例对象,你会模拟哪个接口?

标签: scala testing playframework mocking singleton


【解决方案1】:

使用 ScalaMock 3 模拟单例对象不可能,但 Paul Butcher 希望在 ScalaMock 4 中重新引入此功能(请参阅 http://paulbutcher.com/2014/04/15/scalamock-status-report/

【讨论】:

    【解决方案2】:

    不要嘲笑单身人士。代替 WS,让您的服务组件依赖于隐藏它的瘦外观:

    trait GeolocationService {
      def ws: (String, Seq[String]) => Promise[Response] = { (url, params) =>
        wsClient.url(serviceProviderEndpoint.get).withQueryString(params: _*).get()
      }
      def getPath(origin: Location, destination: Location): Future[Route]
    }
    

    在您的测试中,只需用一个 mock 覆盖 ws 方法,现在很容易创建:

    val mockedWs = mock[(String, Seq[String]) => Promise[Response]]
    // TODO specify mock's behavior here
    val service = new DefaultGeolocationService() {
      override def ws = mockedWs
    }
    

    【讨论】:

      【解决方案3】:

      我不喜欢改变设计来适应这种限制。基本上我能得到的都是改变设计然后使用mock框架

        //since mocking frameworks on SCALA is not support mocking singleton
        //hacks are either required to change the design or ...
        //I'd rather putting the mocking logic here by myself
        var testUser:Option[User] = None;
      
        def getCurrentUser(request: Request[Object]) = {
          if( testUser != None ){
          testUser;
        }
      

      希望对你有帮助。

      【讨论】:

        【解决方案4】:

        为什么不使用蛋糕图案?

        这有点冗长,但它会解决你的模拟问题。

        trait GeolocationServiceComponent {
           val geolocationService:GeolocationService
           trait GeolocationService {      
              def wsClient
              def getPath(origin:Location, destination: Location): Future[Route]   
           }
        }
        
        trait GeolocationServiceComponentImpl {
           val geolocationService = new GeolocationService {
              override def wsClient = WS
           }
        }
        
        
        class DefaultGeolocationService extends GeolocationServiceComponent ...
        
        //Defined into your test class
        trait MockGeolocationServiceComponent {
               val geolocationService = Mock[GeolocationService]
        //Here you define your mock logic       
        }
        

        你也可以使用 monads 来做到这一点,但我从来没有实现过,这里有描述: Scala dependency injection: alternatives to implicit parameters

        【讨论】:

        • 我不确定你说什么。 GeolocationService 是正在测试的类,我不想模拟它。我想模拟 wcClient。
        • 实际上你不能模拟一个对象,它需要等待scala 2.12,或者完成macron实现才能模拟对象。我尝试使用 cake 模式向您展示一种方法,让您模拟使用 WS 的方法的结果。稍后我会改变我的答案,但基本上你可以扩展你的地理位置覆盖模拟中唯一的方法 wsClient]
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-24
        • 2011-04-04
        • 2013-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多