Reputation: 18639
I have the following code:
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
Now I have Calculator service:
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
Now I would like to mock MyCalculator using Mockito to bring me incorrect result.
"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)
}
}
but I'm still getting "4" instead of "0"
Is there any way to handle such test cases?
P.S: I can change some class or trait implementation, but doing global refactoring might be problematic
Upvotes: 4
Views: 7952
Reputation: 40508
Well, your mock object is not used anywhere, so, it's not a big surprise, that it never gets called, is it?
To answer your question, no, you cannot mock a singleton, and that is why it is almost never a good idea to use it directly like this. External dependencies to components need to be supplied from the outside in order for the components to be testable independently.
One way to do what you want is to make CalculatorService
a class and pass the MyCalculator
instance to the constructor as a parameter:
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
}
}
}
then, in your test, you can just do:
val testMe = new CalculatorService(mock[MyCalculator])
If it has to remain a trait for some reason, you can use "cake pattern" to supply external dependencies:
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]
}
Upvotes: 8