【问题标题】:Mockito for Objects in ScalaScala 中对象的 Mockito
【发布时间】:2013-05-08 14:50:52
【问题描述】:

我正在使用 Scala 2.10、specs2 和 Mockito。我想模拟 scala.io.Source.fromURL()。问题似乎是 fromURL() 是 io.Source's object 中的一个函数。

val m = mock[io.Source]
m.fromURL returns io.Source.fromString("Some random string.")

这是单元测试中非常简单的模拟。为什么它不起作用?

谢谢!

【问题讨论】:

    标签: scala object mockito


    【解决方案1】:

    您可以尝试spying,而不是嘲笑它:

    val m = spy(io.Source)
    

    或者你可以模拟如下:

    val m = mock[io.Source.type]
    

    但是你是如何在你正在测试的课程中使用Source 的呢?如果你有这样的示例类:

    class MyClass{
    
      def foo = {
        io.Source.doSomething //I know doSomething is not on Source, call not important
      }
    }
    

    然后为了利用模拟/间谍,你必须像这样构造你的类:

    class MyClass{
      val source = io.Source
      def foo = {
        source.doSomething
      }
    }
    

    然后你的测试必须看起来像这样:

    val mockSource = mock[io.Source.type]
    val toTest = new MyClass{
      override val source = mockSource
    }
    

    在 Java 世界中,静态方法是模拟的祸根。在 Scala 世界中,对对象的调用对于单元测试来说也很麻烦。但是如果你按照上面的代码,你应该能够在你的类中正确地模拟出一个基于对象的依赖。

    【讨论】:

    • 很好的答案!我是 Scala 的新手,我觉得它很麻烦。正如朋友所说,它看起来类似于依赖注入模式。
    • 为什么模拟 io.BufferedSource 的 mkString 不起作用?这是一个实例方法:gist.github.com/Marius-Stroe/5545901
    • 我收到一个错误:Mockito 无法模拟/监视以下内容:-最终类-匿名类-原始类型
    • 是否可以对对象做类似的事情?
    • 嘿@GismoRanas 你如何解决这个问题我面临同样的错误。
    【解决方案2】:

    好消息!使用最新的 1.16 版本的 mockito-scala,您现在可以模拟 scala objects。

    要启用 withObjectMocked 功能,必须创建包含一行的文件 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:

    mock-maker-inline 
    

    例子:

    object FooObject {   
      def simpleMethod: String = "not mocked!"
    }
    
    "mock" should {
      "stub an object method" in {
        FooObject.simpleMethod shouldBe "not mocked!"
    
        withObjectMocked[FooObject.type] {
          FooObject.simpleMethod returns "mocked!"
          //or
          when(FooObject.simpleMethod) thenReturn "mocked!"
    
          FooObject.simpleMethod shouldBe "mocked!"
        }
    
        FooObject.simpleMethod shouldBe "not mocked!"
      } 
    }
    

    见:https://github.com/mockito/mockito-scala#mocking-scala-object

    【讨论】:

    • 这不会像 PowerMock 模拟静态对象那样模拟对象;后者通过修改原始对象的字节码来完成它,而在我的测试中,withObjectMocked 似乎通过子类化创建了一个新实例。除非注入了对模拟的引用,否则被测代码仍将引用原始对象。
    【解决方案3】:

    多年后,上述答案不像其他人指出的那样有效。

    还有你 cannot mock scala.io.Source 对象。

    我可以模拟最终/私有方法或类吗?这是不支持的,因为使用宏生成的模拟被实现为模拟类型的子类。所以私有和最终方法不能被覆盖。您可能想尝试在代码中使用适配器或外观以使其可测试。最好针对特征/接口而不是具体实现进行测试。有一些库支持这种模拟,例如 PowerMock。请注意,这种模拟涉及字节码操作,这有可能使您的测试副本与实际实现有所不同。

    所以我所做的是将scala.io.Source.fromUrl() 抽象为函数参数并在测试中传入模拟函数。

      // original func
      def aFuncThatUsesSource() = {
        val source = scala.io.Source("127.0.0.1:8080/...")
        val result = source.mkString
        Try(source.close())
        result
      }
    
      // test friendly func that accepts `scala.io.Source.fromURL` as arg
      def aTestFriendlyFunc(makeApiCall: String => BufferedSource) = {
        val source = makeApiCall("127.0.0.1:8080/...")
        val result = source.mkString
        Try(source.close())
        result
      }
    
    ....
    
      // test spec
      def testyMcTesterson = () => {
        val makeApiCall = mockFunction[String, BufferedSource]
        makeApiCall.expects("something...")
          .returns( new BufferedSource(new ByteArrayInputStream("returns something".getBytes)) )
        aTestFriendlyFunc(makeApiCall) shouldEqual "returns something"
      }
    

    【讨论】:

      猜你喜欢
      • 2017-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多