有没有办法在 Scala 中模拟 Singleton 对象

Is there any way to mock Singleton object in Scala

我有以下代码:

trait Calculator {

  def add(x:Int, y:Int):Int
  def multiply(x:Int,y: Int):Int
}

trait MyCalculator extends Calculator {

  override def add(x: Int, y: Int): Int = x+y //in real live it calls remote service which is not avaialble in test

  override def multiply(x: Int, y: Int): Int = x*y //in real live it calls remote service which is not avaialble in test
}

object MyCalculator extends MyCalculator

现在我有计算器服务了:

trait CalculatorServiceTrait {

  def calculate(x:Int,sign:String,y:Int):Int
}

trait CalculatorService extends CalculatorServiceTrait{
  override def calculate(x: Int, sign: String, y: Int): Int = {
    sign match{
      case "+" => MyCalculator.add(x,y)
      case "*" => MyCalculator.multiply(x,y)
      case _ => 0
    }
  }
}

object CalculatorService extends CalculatorService

现在我想使用 Mockito 模拟 MyCalculator 给我带来不正确的结果。

 "Calculator Service" should{

    "return 0 when 2 and 2 used " in{

      val MyCalculatorMock = mock[MyCalculator]
      when(MyCalculatorMock.multiply(2,2)).thenReturn(0)
      class CalculatorServiceUnderTest extends CalculatorService with MyCalculator

      new CalculatorServiceUnderTest with MyCalculator
      val c = new CalculatorServiceUnderTest
      val result = c.calculate(2,"+",2)
      result shouldEqual(0)
    }
  }

但我仍然得到“4”而不是“0”

有没有办法处理这样的测试用例?

P.S: 我可以更改一些 class 或特征实现,但进行全局重构可能会有问题

嗯,你的模拟对象没有在任何地方使用,所以,它永远不会被调用并不奇怪,不是吗?

要回答你的问题,不,你不能模拟单例,这就是为什么像这样直接使用它几乎不是一个好主意。需要从外部提供组件的外部依赖关系,以便组件可以独立测试。

做你想做的事情的一种方法是使 CalculatorService 成为 class 并将 MyCalculator 实例作为参数传递给构造函数:

class CalculatorService(calc: MyCalculator = MyCalculator) 
   extends CalculatorServiceTrait {
     override def calculate(x: Int, sign: String, y: Int): Int = sign match {
       case "+" => calc.add(x,y)
       case "*" => calc.multiply(x,y)
       case _ => 0
    }
  }
}

然后,在你的测试中,你可以这样做: val testMe = new CalculatorService(mock[MyCalculator])

如果由于某种原因必须保留特征,您可以使用 "cake pattern" 提供外部依赖项:

trait CalculatorProvider {
  def calc: MyCalculator
}

trait CalculatorService { self: CalculatorProvider => 
 ...
}

object CalculatorService extends CalculatorService with CalculatorProvider {
  def calc = MyCalculator
}


val testMe = new CalculatorService with CalculatorProvider {
  val calc = mock[MyCalculator]
}