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

时间:2016-06-21 14:31:18

标签: scala unit-testing mockito scalatest

我有以下代码:

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:我可以更改某些类或特征实现,但进行全局重构可能会有问题

1 个答案:

答案 0 :(得分:4)

嗯,你的模拟对象并没有在任何地方使用,因此,它不会是一个大惊喜,它永远不会被调用,是吗?

要回答你的问题,不,你不能嘲笑一个单身人士,这就是为什么直接使用它这样几乎不是一个好主意。需要从外部提供对组件的外部依赖性,以便组件可以独立测试。

执行所需操作的一种方法是使CalculatorService成为一个类,并将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])

如果由于某种原因必须保持特性,你可以使用"蛋糕模式"提供外部依赖:

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]
}