1

I have a question about using FunSuite with Matchers.

I'm trying to compare to lists of objects so I tried doing this:

orders2 should be(tiOrdersList2)

where orders2 and tiOrdersList2 are scala.List[MyObject]

This match fails miserably, so I tried with

orders2.toSeq should be (tiOrdersList2.toSeq)

no luck

then I tried matching the single items

orders2(0) should be (tiOrdersList2(0))

no luck again

at the end I found that to make it work for the single object I had to use "===" and going back to the first case I managed to get this working:

orders2 === tiOrdersList2

I can't understand what is the difference here. I imagine that the "should be" matcher is stricter than "===" but I can't see why. Can anyone explain that to me?

NOTE: I want to point out that MyObject has various properties of different java based types, including float. could be that the reason?

UPDATE I checked the matcher with simple classes (see snippet below) and if I override the equals method on my own, also the "should be" works, so the main question is what kind of comparison is "===" doing?

import org.scalatest.{Matchers, FunSuite}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner

@RunWith(classOf[JUnitRunner])
class blahSuite extends FunSuite  with Matchers
{
    test("extractSessions") {
        new Object1 === (new Object1)
        new Object2 === (new Object2)
        new Object1 shouldEqual  (new Object1)
        new Object2 should be (new Object2)
    }

    class Object1 {
        val pippo: java.lang.String = "blah"
        val pappo:java.lang.Long = 0l
        override def toString(): String = {
            s"$pippo, $pappo"
        }
    }
    class Object2 {
        val pippo: java.lang.String = "blah"
        val pappo:java.lang.Long = 0l
        val pluto:java.lang.Float = 0.0f
        override def toString(): String = {
            s"$pippo, $pappo, $pluto"
        }
    }
}

UPDATE2 it seems that "===" compares very loosely: I tried with other instances where the content of an object is different (i.e. different "pippo" strings) and it tells me they are the same. So it seems it compares the class types rather than doing some magic with reflection... I'm getting more and more confused about this and starting to think that I should implement my equals method and forget about this...

gmcontessa
  • 558
  • 4
  • 10
  • Perhaps they're sorted differently and the equals check is false because of that? – John Starich Oct 28 '15 at 18:55
  • nope, I tried doing it on the single objects as well. Even checking the sets by printing out the content (I have some toString methods implemented) and looking into each object with the debugger. – gmcontessa Oct 28 '15 at 22:51
  • Could you add the specific test that's failing? The ones mentioned in your first update don't describe lists/sequences you talked about in the original question. Or are you narrowing your question to equality regarding just Object1 and Object2? – John Starich Oct 29 '15 at 01:19

1 Answers1

2

About ===

I think === is not exactly what you thought. You should use assert(1 === 4). As pointed out in scalatest doc:

ScalaTest lets you use Scala's assertion syntax, but defines a triple equals operator (===) to give you better error messages. The following code would give you an error indicating only that an assertion failed:

assert(1 == 2) 

Using triple equals instead would give you the more informative error message, "1 did not equal 2":

assert(1 === 2)

So === is the same as ==.

(Note that you can also have === in matchers like result should === (3) as mentioned in the docs, but I don't think this is really important for you right now, just for you to know they are different beasts)

Should be (working?)

Should be is not working also. The thing is that the error is thrown for the first failed assert (shouldEqual) and execution stops, so we have never ran the should be. If you change the order, you will see the error for should be also:

new Object2 should be (new Object2)
new Object1 shouldEqual  (new Object1)

[info] - extractSessions *** FAILED ***
[info]   blah, 0, 0.0 was not equal to blah, 0, 0.0 (testa.scala:22)

Comparison

As you already find out, we can't just compare objects:

val a = new Object1
val b = new Object1

println(a == b) // false

// Or the equivalent version

println(a.equals(b)) // false

Roughly speaking, under the hood when you call == you are actually calling something like this:

println(a.hashCode == b.hashCode) // false

And they are not obviously not the same since hashCode's default implementation is based in the memory address of the current object:

println(a.hashCode)  // 769724695
println(b.hashCode)  // 757278160

So yes, you will have to...

Reimplement equals and hashCode

Like what was done in this Scala Cookbook recipe, you could do something like this:

test("extractSessions") {
  new Object3 should be (new Object3)
  new Object3 shouldEqual (new Object3)
}

class Object3 {
  // PS: A little code improvement: 
  // I removed the old ugly, java like
  // val pippo: java.lang.String = "blah" 
  // with the more scala-like:  
  val pippo = "blah"  // Scala guesses the type -> String
  val pappo = 0L      // again -> Long

  def canEqual(a: Any) = a.isInstanceOf[Object3]

  override def equals(that: Any): Boolean =
    that match {
      case that: Object3 => that.canEqual(this) && this.hashCode == that.hashCode
      case _             => false
  }

  override def hashCode: Int = (41 * (41 + pippo.hashCode) + pappo.hashCode)
}

So... I always have to reimplement hashCode and equals for my objects. So is this it? That's all?

No!

Scala can do more for you!

The case class

When you use the keyword case before a class definitions you create a case class. This, along with other nice features, automatically implements the hashcode, equals and toString methods for you.

So now, we can have have something like this:

// ...

test("extractSessions") {
  assert(Object1("blah", 0L) === Object1("blah", 0L))
  assert(Object2("blah", 0L, 0.0F) === Object2("blah", 0L, 0.0F))

  Object1("blah", 0L) shouldEqual (Object1("blah", 0L))
  Object2("blah", 0L, 0.0F) should be (Object2("blah", 0L, 0.0F))

  // Also works
  new Object1("blah", 0L) shouldEqual (new Object1("blah", 0L))
}

case class Object1(pippo: String, pappo: Long)

case class Object2(pippo: String, pappo: Long, pluto: Float)

// ...    

If you really want to go deeper and want to understand more deeply the equality solutions and pitfalls, I recommend the article How to Write an Equality Method in Java from Martin's Odersky to understand a little bit of the equality problems in Java. That helps understand why some things are the way they are in Scala.

Community
  • 1
  • 1
Onilton Maciel
  • 3,559
  • 1
  • 25
  • 29
  • Thanks Onilton. The "Should be" explanation is very useful and clarifies me why my example wasn't working. I have still some doubt about "===": as you mentioned, it's the same as "==" apart from having more details and doing a deep "==" call in case the objects compared are array. I attached another example where "==" returns true even if I don't have case classes or hashcode/equals override. do you have any idea why "==" returns true? – gmcontessa Oct 29 '15 at 09:43
  • Sorry, I just re-tested and read more carefully your answer and understood that for the "==" case my problem was not adding "assert" before the check. All clear now. thanks! – gmcontessa Oct 29 '15 at 09:52