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