Pengin Pengin - 3 months ago 28
Scala Question

Scala testing with type enrichment

I've become attached to type enrichment, for example

object MyImplicits{
implicit class RichInt(i: Int){
def complexCalculation: Int = i * 200
}
}


Which I use in code like this

object Algorithm{
def apply(rand: Random) = {
import MyImplicits._
rand.nextInt.complexCalculation + 1
}
}


But how I can isolate and unit test Algorithm now? In particular, I'd like to mock the implemention of
complexCalculation
, something like this:

class MyAlgorithmTest extends FreeSpec with MockitoSugar{
import org.mockito.Mockito.when

"MyApgorithm" {
"Delegates complex calculation" in {
val mockRandom = mock[Random]
when(mockRandom.nextInt()).thenReturn(1)

// This wouldn't work, but is the kind of thing I'm looking for
//when(1.complexCalculation).thenReturn(2)
val expected = 1 * 2 + 1

val result = MyAlgorithm(mockRandom)
assert(result === expected)
}
}
}

Answer

RichInt.scala

trait RichInt {
  def complexCalculation: Int
}

class RichIntImpl(i: Int) extends RichInt {
  def complexCalculation = i * 200
}

Algorithm.scala

import scala.util.Random

class Algorithm(enrich: Int => RichInt) {
  implicit val _enrich = enrich
  def apply(rand: Random) = {
    rand.nextInt.complexCalculation + 1
  }
}

object Algorithm extends Algorithm(new RichIntImpl(_))

AlgorithmTest.scala

import org.scalatest.FreeSpec
import scala.util.Random
import org.mockito.Mockito._

class AlgorithmTest extends FreeSpec with MockSugar {

  "MyApgorithm should" - {
    "Delegate the complex calculation" in {
      val mockRandom = mock[Random]
      when(mockRandom.nextInt()) thenReturn 1

      val algorithm = new Algorithm(
        enrich = mocking[Int => RichInt] { enrich =>
          when(enrich(1)).thenReturnMocking { richInt =>
            when(richInt.complexCalculation).thenReturn(2)
          }
        }
      )

      val expected = 3

      assert(algorithm(mockRandom) === expected)
    }
  }
}

MockSuger.scala

import org.scalatest.mockito.MockitoSugar
import org.mockito.stubbing.OngoingStubbing

// More sugars to make our tests look better.
trait MockSugar extends MockitoSugar {

  def mocking[T <: AnyRef : Manifest](behavior: T => Unit): T = {
    val m = mock[T]
    behavior(m)
    m
  }

  implicit class RichOngoingStubbing[T <: AnyRef : Manifest](stub: OngoingStubbing[T]) {
    def thenReturnMocking(behavior: T => Unit) = {
      val m = mock[T]
      val s = stub.thenReturn(m)
      behavior(m)
      s
    }
  }
}