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)
    }
  }
}
                Implicits enable composition, and when you have composition you typically don't need mocks, because you can substitute implementation for testing. That being said, I'm not a big fan of implicits in this case, just don't see the value they bring. I'd solve it with old school composition (as hinted in my other comment):
trait Calculation {
  def calculation(i: Int): Int
}
trait ComplexCalculation extends Calculation {
  def calculation(i: Int): Int = i * 200
}
trait MyAlgorithm {
  self: Calculation =>
  def apply(rand: Random) = {
    calculation(rand.nextInt) + 1
  }
}
// somewehre in test package
trait MockCalculation extends Calculation {
  def calculation(i: Int): Int = i * 200
}
//test instance
object MyAlgorithm extends MyAlgorithm with MockCalculation
If you insist on using implicits to do composition, you can do this:
trait Computation {
  def compute(i: Int): Int
}
object prod {
  implicit val comp = new Computation {
    def compute(i: Int): Int = i * 200
  }
}
object test {
  implicit val comp = new Computation {
    def compute(i: Int): Int = i + 2
  }
}
object Algorithm {
  def apply(rand: Random)(implicit comp: Computation) = {
    comp.compute(i) + 1
  }
}
// application site
import prod._
Algorithm(scala.util.Random) // will run * 200 computation
//test
import test._
Algorithm(scala.util.Random) // will run + 2 computation
This won't give you a dot syntax for computation though. My gut also goes against this approach because this is a very subtle way of defining behavior and it is easy to make a mistake with what import to bring.
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
    }
  }
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With