Search code examples
scalascalatest

ScalaTest deep comparison best practices


I'm trying to write a unit test for a function that returns a tuple of a class that contains an array.

A simple assert(out === expectedOut) or out should be(expectedOut) does not compare the contents of the classes on the LHS and RHS because of the Array. Is there a neat way to do that in ScalaTest?

I've looked at custom matchers but I'm not sure if that's the best way for what I'm trying to do. So any info from experience of the experts would be much appreciated.

Edit: Here is a case where that does not seem to be the case:

object Utils {
  case class Product(id: Int, prices: Array[Int])


  def getProductInfo(id: Int, prices: Array[Int]): Option[Product] = {
    val sortedPrices = prices.sortWith(_ < _)
    Some(Product(id, sortedPrices))
  }
}

---

import org.scalatest._
import Utils._

class DataProcessorSpec extends FlatSpec with Matchers with OptionValues {
  val id = 12345
  val priceList = Array(10,20,30)

  val prod = Utils.getProductInfo(id, priceList)
  val expectedProd = Some(Utils.Product(id, priceList))

  "A DataProcessorSpec" should "return the correct product information" in {
     prod should be(expectedProd)
  }
}

The test fails because the sortWith causes a new array to be created and is thus pointing to a different memory location, as far as I can tell.


Solution

  • UPDATE: with the code example, this is clearer:

    Comparison fails because shoud be uses the case class's equals function to perform the comparison, and case classes don't compare arrays "deeply" - which means, as you suspected, that different instances will not be equal (see more info here).

    Workarounds:

    1. Verify equality of each "part" of the case class separately:

      prod.get.prices should be(expectedProd.get.prices)
      prod.get.id should be(expectedProd.get.id)
      
    2. If using Array is not a must, you can change the case class to use Seq[Int], which would make the test pass, because a Seq's implementation of equals is "deep"

    Comparing Arrays:

    When compared "on their own", arrays are compared as expected ("deeply") by Matchers' should be:

    arr1 should be(arr2) // true if contents is the same
    

    If you just want to compare the contents, without verifying that out is indeed an Array, you can use theSameElementsInOrderAs:

    arr1 should contain theSameElementsInOrderAs arr2