0

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

Mockito: How to verify a method was called only once with exact parameters ignoring calls to other methods?

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.
ssj_100
  • 149
  • 1
  • 11
  • I'm gonna try to take a step back, what's the reason you want to test how many time the methods call to eachother? – ultrasecr.eth Dec 23 '19 at 19:00
  • Assuming you have to process a very large list and your code should be able to reuse any calculations you've done if you called the higher level functions. For example, if you called for z first and then called for x, the code should not do any re-calculations. I would like my test cases to always check this behavior to guarantee this performance – ssj_100 Dec 25 '19 at 05:37

2 Answers2

1

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.

ultrasecr.eth
  • 1,437
  • 10
  • 13
  • This is exactly what I was looking for, but I can't seem to get sum(*), wasCalled, and once to work. I tried using Mockito.spy but when I use the asterisk or try calling "wasCalled" it get cannot resolve symbol errors. Are these methods coming from a different package? – ssj_100 Dec 30 '19 at 09:22
  • Yes, mockito-scala abstract you completely from mockito-core, so you should never use the `org.mockito.Mockito` object or any other of the core/java classes, the pattern in mockito-scala is similar to scalatest, you mix-in the traits you need (I've updated the example to show that). Please consider marking the answer as the correct one if it solved your problem :) – ultrasecr.eth Dec 30 '19 at 13:43
0

As you mentioned the Mockito is the way to go, here is an example:

class NumberOPSTest extends FunSuite with Matchers with Mockito {

  test("testSum") {
    val listoperations = smartMock[NumberOPS]
    when(listoperations.sum(any)).thenCallRealMethod()

    listoperations.sum(List(2, 4, 4)) shouldEqual 10

    verify(listoperations, never()).sqrt(any)
  }

}
  • Hi, thanks for the suggestion, this looks like what I need but I can't figure where smarkMock is coming from. Also on a somewhat tangent question. I've noticed the syntax mock[className] in several examples but what happens when the class's default constructor requires parameters? – ssj_100 Dec 30 '19 at 09:05
  • You mock it also, when(mainObject.getAttribute("search")).thenReturn(someObject) make sure Re you accept an answer to your question –  Dec 31 '19 at 11:39