I am new to scala, and I'm trying figure out the best way to test the following process.
I have a class that gets a list of numbers from constructor parameter. The class supports various operations on the list, some operations may depend on the output of other operations. But every option should only perform calculations on demand and should be done at most once. No calculations should be done in the constructor.
Example class definition .
InputList: List[Int] .
x: returns a vector with the square of all elements in InputList .
y: returns the sum of all elements in x .
z: returns the square root of y .
As for class implementation, I think I was able to come up with a fitting solution but now I can't figure out how can I test the calculations of the dependent tree of operations are executed only once.
Class Implementation Approach #1:
class Operations(nums: List[Int]) {
lazy val x: List[Int] = nums.map(n => n*n)
lazy val y: Int = x.sum
lazy val z: Double = scala.math.sqrt(y)
}
This was my first approach which I'm confident will do the job but could not figure out how to properly test it so I decided to add some helper methods to confirm they are being called just ones
Class Implementation Approach #2:
class Ops(nums: List[Int]) {
def square(numbers: List[Int]): List[Int] = {
println("calling square function")
numbers.map(n => n*n)
}
def sum(numbers: List[Int]): Int = {
println("calling sum method")
numbers.sum
}
def sqrt(num: Int): Double = {
println("calling sqrt method")
scala.math.sqrt(num)
}
lazy val x: Vector[Double] = square(nums)
lazy val y: Double = sum(x)
lazy val z: Double = sqrt(y)
}
I can now confirm each dependent method of each method is called just once whenever necessary.
Now how can I write tests for these processes. I've seen a few posts about mockito and looked at the documentation but was not able to find what I was looking for. I looked at the following:
Shows how to test whether a function is called once but then how to test whether other depend functions where called? http://www.scalatest.org/user_guide/testing_with_mock_objects#mockito
Seems promising but I can't figure out the syntax:
https://github.com/mockito/mockito-scala
Example Tests I'd like to perform
var listoperations:Ops = new Ops(List(2,4,4))
listoperations.y // confirms 36 is return, confirms square and sum methods were called just once
listoperations.x // confirms List(4,16,16) and confirms square method was not called
listoperations.z // confirms 6 is returned and sqrt method called once and square and sum methods were not called.
Ok, lets leave the pre-mature optimisation argument for another time.
Mocks are meant to be used to stub/verify interactions with dependencies of your code (aka other classes), not to check internals of it, so in order to achieve what you want you'd need something like this
class Ops {
def square(numbers: List[Int]): List[Int] = numbers.map(n => n*n)
def sum(numbers: List[Int]): Int = numbers.sum
def sqrt(num: Int): Double = scala.math.sqrt(num)
}
class Operations(nums: List[Int])(implicit ops: Ops) {
lazy val x: List[Int] = ops.square(nums)
lazy val y: Int = ops.sum(x)
lazy val z: Double = ops.sqrt(y)
}
import org.mockito.{ ArgumentMatchersSugar, IdiomaticMockito}
class IdiomaticMockitoTest extends AnyWordSpec with IdiomaticMockito with ArgumentMatchersSugar
"operations" should {
"be memoised" in {
implicit val opsMock = spy(new Ops)
val testObj = new Operations(List(2, 4, 4))
testObj.x shouldBe List(4, 16, 16)
testObj.y shouldBe 36
testObj.y shouldBe 36 //call it again just for the sake of the argument
testObj.z shouldBe 6 //sqrt(36)
testObj.z shouldBe 6 //sqrt(36), call it again just for the sake of the argument
opsMock.sum(*) wasCalled once
opsMock.sqrt(*) wasCalled once
}
}
}
Hope it makes sense, you mentioned you're new to scala, so I didn't wanna go too crazy with implicits
so this is a very basic example in which the API of your original Operations
class is the same, but it extracts out the heavy lifting to a third party that can be mocked so you can verify the interactions.