【问题标题】:Is it possible to mock / override dependencies / imports in Scala?是否可以在 Scala 中模拟/覆盖依赖项/导入?
【发布时间】:2018-08-09 02:07:54
【问题描述】:

我有一些看起来像这样的代码:

package org.samidarko.actors

import org.samidarko.helpers.Lib

class Monitoring extends Actor {

  override def receive: Receive = {
    case Tick =>
       Lib.sendNotification()
    }
}

有没有办法从 ScalaTest 中模拟/存根 Lib,就像使用 nodejs 的 proxyquire 一样?

我读到我可以使用依赖注入,但我不想这样做

我唯一的选择是将我的 lib 作为类参数传递吗?

class Monitoring(lib: Lib) extends Actor {

有什么建议可以让它更容易测试吗?谢谢

编辑:

Xavier Guihot 的回答是解决问题的一种有趣方法,但我选择更改代码以进行测试。

我将Lib 作为参数传递,并使用mockito 进行模拟,它使代码更容易测试和维护,而不是隐藏范围。

【问题讨论】:

    标签: scala unit-testing mocking scalatest scalamock


    【解决方案1】:

    这个答案只使用scalatest,不影响源代码:

    基本解决方案:

    假设你有这个 src 类(你想要测试并且你想要模拟依赖的那个):

    package com.my.code
    
    import com.lib.LibHelper
    
    class MyClass() {
      def myFunction(): String = LibHelper.help()
    }
    

    还有这个库依赖(你想在测试 MyClass 时模拟/覆盖):

    package com.lib
    
    object LibHelper {
      def help(): String = "hello world"
    }
    

    这个想法是在您的测试文件夹中创建一个类,它将覆盖/隐藏库。该类将与您要模拟的类具有相同的名称和相同的包。在src/test/scala/com/external/lib 中,您可以创建包含以下代码的LibHelper.scala:

    package com.lib
    
    object LibHelper {
      def help(): String = "hello world - overriden"
    }
    

    这样你就可以用通常的方式测试你的代码了:

    package com.my.code
    
    import org.scalatest.FunSuite
    
    class MyClassTest extends FunSuite {
      test("my_test") {
        assert(new MyClass().myFunction() === "hello world - overriden")
      }
    }
    

    改进的解决方案允许为每个测试设置模拟行为:

    以前的代码清晰简单,但 LibHelper 的模拟行为对于所有测试都是相同的。并且可能希望 LibHelper 的一种方法产生不同的输出。因此,我们可以考虑在 LibHelper 中设置一个可变变量,并在每次测试之前更新该变量,以便设置 LibHelper 所需的行为。 (这仅在 LibHelper 是一个对象时才有效)

    阴影 LibHelper(src/test/scala/com/external/lib 中的那个)应该替换为:

    package com.lib
    
    object LibHelper {
    
      var testName = "test_1"
    
      def help(): String =
        testName match {
          case "test_1" => "hello world - overriden - test 1"
          case "test_2" => "hello world - overriden - test 2"
        }
    }
    

    最大规模的类应该变成:

    package com.my.code
    
    import com.lib.LibHelper
    
    import org.scalatest.FunSuite
    
    class MyClassTest extends FunSuite {
      test("test_1") {
        LibHelper.testName = "test_1"
        assert(new MyClass().myFunction() === "hello world - overriden - test 1")
      }
      test("test_2") {
        LibHelper.testName = "test_2"
        assert(new MyClass().myFunction() === "hello world - overriden - test 2")
      }
    }
    

    非常重要的精度,因为我们使用的是全局变量,所以必须强制 scalatest 按顺序(而不是并行)运行测试。相关的 scalatest 选项(将包含在 build.sbt 中)是:

    parallelExecution in Test := false
    

    【讨论】:

    • 不错。我确实尝试隐藏 LibHelper,但在创建类实例之前在测试范围内创建对象。有没有办法对LibHelper 的结果进行存根,以使您的解决方案更加灵活?
    • 您向我展示了如何通过将和 Object 替换为另一个对象来隐藏范围。我想知道是否可以对方法进行存根/模拟以使其更具动态性,例如从一个测试到另一个测试给出不同的答案?
    • @samidarko 更新了答案;希望这会有所帮助。
    • 有趣的解决方案。谢谢
    • @XavierGuihot 您如何确保测试调用的是阴影对象而不是原始对象?这不取决于类加载器吗?
    【解决方案2】:

    不是一个完整的答案(因为我不太了解 AOP),但为了让您朝着正确的方向前进,这可以通过名为 AspectJ 的 Java 库实现:

    https://blog.jayway.com/2007/02/16/static-mock-using-aspectj/

    https://www.cakesolutions.net/teamblogs/2013/08/07/aspectj-with-akka-scala

    伪代码示例(不详述):

    class Mock extends MockAspect {
        @Pointcut("execution (* org.samidarko.helpers.Lib.sendNotification(..))")
        def intercept() {...}
    
    }
    

    这种方法的底层基础是动态代理:https://dzone.com/articles/java-dynamic-proxy。但是,您也可以模拟静态方法(也许您必须在模式中添加单词 static)。

    【讨论】:

      猜你喜欢
      • 2020-04-14
      • 2014-03-18
      • 1970-01-01
      • 2019-05-20
      • 1970-01-01
      • 2020-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多