danny.lesnik
danny.lesnik

Reputation: 18639

Is there any way to mock Singleton object in Scala

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

Answers (1)

Dima
Dima

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

Related Questions