是否可以在Scala中模拟/覆盖依赖项/导入?

时间:2018-03-01 03:53:59

标签: scala unit-testing mocking scalatest scalamock

我有一些代码如下:

package org.samidarko.actors

import org.samidarko.helpers.Lib

class Monitoring extends Actor {

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

对于 ScalaTest 模拟/存根 Lib是否可以像proxyquire一样使用nodejs?

我读过我可以使用依赖注入,但我宁愿不这样做

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

class Monitoring(lib: Lib) extends Actor {

有什么建议让它更可测试吗?感谢

编辑:

Xavier Guihot的答案是一个有趣的方法,但我选择更改代码以进行测试。

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

2 个答案:

答案 0 :(得分:4)

此答案仅使用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

答案 1 :(得分:1)

不是一个完整的答案(因为我不太了解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)。