Re: [scalatest-users] Custom matcher for Set types

88 views
Skip to first unread message

Bill Venners

unread,
Apr 30, 2013, 10:40:20 AM4/30/13
to scalate...@googlegroups.com
Hi Andrew,

Can you elaborate on what diffs you'd like to see. I'm not sure I know what you're getting with JUnit. I tried this and see similar output to ScalaTest:

scala> assertEquals(s1, s2)
java.lang.AssertionError: expected:<[1, 2, 3]> but was:<[1, 2, 4]>

Are you perhaps talking about diffs you can get in IDEs when using JUnit? Also, what does &~ do?

Thanks.

Bill


On Tue, Apr 30, 2013 at 7:27 AM, Andrew Charles <acha...@gmail.com> wrote:
Hi I'm trying to write a simple custom matcher that will compare two Sets, and if they're not equal display the differences between the two.

Unfortunately the way I've written it won't compile.  It says it can't find an overload for the should matcher that would match the Matcher I provided.

Is there something else I can provide so it all works, or will the framework not allow this yet?  I'm using ScalaTest 2.0M5b for Scala 2.10.1

/**
 * This matcher compares two sets and displays the difference if they do not match
 */
trait SetMatcher {
def equal[T](right: Set[T]): Matcher[Set[T]] =
     new Matcher[Set[T]] {
        def apply(left: Set[T]): MatchResult = {
          
          MatchResult(
            left == right,
            s"""$left did not equal $right
                |left &~ right = ${left &~ right}
            |right &~ left = ${right &~ left}""".stripMargin,       
            s"$left equaled $right and it shouldn't have"
          )
        }
      }
}

Also, as an aside are their plans to provide better output for when collections/arrays don't match like JUnit does?  

--
--
You received this message because you are subscribed to the Google
Groups "scalatest-users" group.
To post to this group, send email to scalate...@googlegroups.com
To unsubscribe from this group, send email to
scalatest-use...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/scalatest-users?hl=en
ScalaTest itself, and documentation, is available here:
http://www.artima.com/scalatest
---
You received this message because you are subscribed to the Google Groups "scalatest-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scalatest-use...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Bill Venners
Artima, Inc.
http://www.artima.com

Andrew Charles

unread,
Apr 30, 2013, 10:56:52 AM4/30/13
to scalate...@googlegroups.com


On Tuesday, April 30, 2013 10:40:20 AM UTC-4, Bill Venners wrote:
Hi Andrew,

Can you elaborate on what diffs you'd like to see. I'm not sure I know what you're getting with JUnit. I tried this and see similar output to ScalaTest:


Sure can.  For example if I compare two sets of numbers  Set(1,2,3) and Set(3, 4, 5) I should see the following output:

Set(1,2,3) did not equal Set(3,4,5) 
left &~ right = Set(4,5)
right &~ left = Set(1,2)
 
Basically &~ provides the difference between two Sets (there is also a "diff" method which does the same thing).  I can use this to see exactly why two Sets don't match.  Obviously the real world example consists of more complicated objects that aren't easily compared by eye.



scala> assertEquals(s1, s2)
java.lang.AssertionError: expected:<[1, 2, 3]> but was:<[1, 2, 4]>

Are you perhaps talking about diffs you can get in IDEs when using JUnit? Also, what does &~ do?


Hmm..maybe I'm thinking of Strings only, which scalatest seems to support.  I could have sworn JUnit did something similar to arrays but I guess I was imagining things.

Either way, it would be a useful feature to have for comparing collections of more complicated objects.

Bill Venners

unread,
Apr 30, 2013, 11:16:11 AM4/30/13
to scalate...@googlegroups.com
Hi Andrew,


On Tue, Apr 30, 2013 at 7:56 AM, Andrew Charles <acha...@gmail.com> wrote:


On Tuesday, April 30, 2013 10:40:20 AM UTC-4, Bill Venners wrote:
Hi Andrew,

Can you elaborate on what diffs you'd like to see. I'm not sure I know what you're getting with JUnit. I tried this and see similar output to ScalaTest:


Sure can.  For example if I compare two sets of numbers  Set(1,2,3) and Set(3, 4, 5) I should see the following output:

Set(1,2,3) did not equal Set(3,4,5) 
left &~ right = Set(4,5)
right &~ left = Set(1,2)
 
Basically &~ provides the difference between two Sets (there is also a "diff" method which does the same thing).  I can use this to see exactly why two Sets don't match.  Obviously the real world example consists of more complicated objects that aren't easily compared by eye.

Ah, that's Scala's method. I was thinking it was your method. Hadn't noticed that one before! I have long been wondering how to improve error message for large collections. It can be very difficult to see how two large collections are different. There's also a diff on Seq, but not one on Map, Iterable, or Traversable. So for sets and seqs at least, your idea could be helpful. The other thing I've considered is just doing the usual string diff on the toStrings of the objects, where square brackets show the differences.



scala> assertEquals(s1, s2)
java.lang.AssertionError: expected:<[1, 2, 3]> but was:<[1, 2, 4]>

Are you perhaps talking about diffs you can get in IDEs when using JUnit? Also, what does &~ do?


Hmm..maybe I'm thinking of Strings only, which scalatest seems to support.  I could have sworn JUnit did something similar to arrays but I guess I was imagining things.

Either way, it would be a useful feature to have for comparing collections of more complicated objects.

One thing that is coming, probably in May, is an enhancement to MatchResult so that the objects that were compared are carried along. This will enable more easy customization of error messages. So that's one thing that will help. For Seq's and Set's, though, perhaps your diffs idea might be a good default.

As far as your trouble with the custom matcher, I'd try renaming it from equal. I suspect you could get rid of the ambiguity by using a different name.

Bill

Andrew Charles

unread,
Apr 30, 2013, 11:24:43 AM4/30/13
to scalate...@googlegroups.com


On Tuesday, April 30, 2013 11:16:11 AM UTC-4, Bill Venners wrote:
Hi Andrew,

As far as your trouble with the custom matcher, I'd try renaming it from equal. I suspect you could get rid of the ambiguity by using a different name.



I tried renaming it to "setEqual" and got the same compiler error message.  For reference here it what it is:

overloaded method value should with alternatives:   (notWord: TimecardBuilderSpec.this.NotWord)TimecardBuilderSpec.this.ResultOfNotWordForTraversable[com.linxberg.ta.core.business.PayInterval,scala.collection.GenTraversable[com.linxberg.ta.core.business.PayInterval]] <and>   (beWord: TimecardBuilderSpec.this.BeWord)TimecardBuilderSpec.this.ResultOfBeWordForAnyRef[scala.collection.GenTraversable[com.linxberg.ta.core.business.PayInterval]] <and>   (haveWord: TimecardBuilderSpec.this.HaveWord)TimecardBuilderSpec.this.ResultOfHaveWordForTraversable[com.linxberg.ta.core.business.PayInterval] <and>   (rightMatcher: org.scalatest.matchers.Matcher[scala.collection.GenTraversable[com.linxberg.ta.core.business.PayInterval]])Unit  

cannot be applied to (org.scalatest.matchers.Matcher[Set[com.linxberg.ta.core.business.PayInterval]])

Andrew Charles

unread,
May 1, 2013, 10:27:33 AM5/1/13
to scalate...@googlegroups.com
I tried doing some more research on the matter and it appears that the implicit wrapper for Traversables is too strict on what can go on the right side of the should verb.

I looked into creating my own wrapper for Sets (which should override the existing GenTraversable wrapper) but then I realized I have to copy and paste a bunch of private infrastructure code, which is ugly.  I also noticed in the latest release the whole Matchers thing is refactored.

However, even in the refactored code I don't think it'll work because there will be no generic should(rightMatcher:Matcher[_]) method.

This is as far as I've gotten. 

Bill Venners

unread,
May 1, 2013, 6:41:17 PM5/1/13
to scalate...@googlegroups.com
Hi Andrew,

Sorry for the delay in continuing this conversation. I just got back from several trips, including a 12 hour time change, and have been losing the battle against jet lag! But I tried this multiple ways and can't see the failure you're seeing. Can you let me know how you got that exactly? When I tried it, it compiled and worked. Here's my SetMatcher class, just copy and pasted from your original:

import org.scalatest.matchers._


/**
 * This matcher compares two sets and displays the difference if they do not match
 */
trait SetMatcher {

    def equal[T](right: Set[T]): Matcher[Set[T]] =

         new Matcher[Set[T]] {

            def apply(left: Set[T]): MatchResult = {
             
              MatchResult(
                left == right,
                s"""$left did not equal $right
                    |left &~ right = ${left &~ right}
                |right &~ left = ${right &~ left}""".stripMargin,      
                s"$left equaled $right and it shouldn't have"
              )
            }
          }

}


I stuck that in a SetMatcher.scala class and compiled it against the current trunk.

First I tried it in the interpreter with imports:

scala> import org.scalatest._
import org.scalatest._

scala> import Matchers._
import Matchers._

scala> Set(1, 2, 3) should equal (Set(1, 2, 4))
org.scalatest.exceptions.TestFailedException: Set(1, 2, 3) did not equal Set(1, 2, 4)
    at org.scalatest.Matchers$$anon$17.newTestFailedException(Matchers.scala:924)

That behaved as expected, now to import the SetMatcher equal method:

scala> object SetMatcher extends SetMatcher
defined module SetMatcher

scala> import SetMatcher._
import SetMatcher._

scala> Set(1, 2, 3) should equal (Set(1, 2, 4))
org.scalatest.exceptions.TestFailedException: Set(1, 2, 3) did not equal Set(1, 2, 4)
left &~ right = Set(3)
right &~ left = Set(4)
    at org.scalatest.Matchers$$anon$17.newTestFailedException(Matchers.scala:924)
    at org.scalatest.Matchers$ShouldMethodHelper$.shouldMatcher(Matchers.scala:5159)
    at org.scalatest.Matchers$AnyShouldWrapper.should(Matchers.scala:5193)
    at .<init>(<console>:18)

So that worked. I decided to try it by mixing in also. I created this Spec class:

import org.scalatest._

class MySpec extends FreeSpec with Matchers with SetMatcher {

  "A Set" - {
    "should do something" in {
      Set(1, 2, 3) should equal (Set(1, 2, 4))
    }
  }
}

I compiled this also, then ran it and got:

Mi-Novia:seoulmerge bv$ scala -cp target/jar_contents/:. org.scalatest.run MySpec
Discovery starting.
Discovery completed in 19 milliseconds.
Run starting. Expected test count is: 1
MySpec:
A Set
- should do something *** FAILED ***
  Set(1, 2, 3) did not equal Set(1, 2, 4)
  left &~ right = Set(3)
  right &~ left = Set(4) (MySpec.scala:8)
Run completed in 185 milliseconds.
Total number of tests run: 1
Suites: completed 1, aborted 0
Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
*** 1 TEST FAILED ***

I did try this off of trunk, so maybe that's why it worked for me, given we are in the middle of a big matchers refactor. But does anything here look different from how you were running?

Bill


--
--
You received this message because you are subscribed to the Google
Groups "scalatest-users" group.
To post to this group, send email to scalate...@googlegroups.com
To unsubscribe from this group, send email to
scalatest-use...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/scalatest-users?hl=en
ScalaTest itself, and documentation, is available here:
http://www.artima.com/scalatest
---
You received this message because you are subscribed to the Google Groups "scalatest-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scalatest-use...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Andrew Charles

unread,
May 2, 2013, 5:22:49 PM5/2/13
to scalate...@googlegroups.com


On Wednesday, May 1, 2013 6:41:17 PM UTC-4, Bill Venners wrote:
Hi Andrew,


I did try this off of trunk, so maybe that's why it worked for me, given we are in the middle of a big matchers refactor. But does anything here look different from how you were running?


That must be it.  I'm trying mine against 2.0 M5, which has the old "regime" of ShouldMatchers actually containing the code.

I'm merely wrote what you pasted there and mixed it in with ShouldMatchers on my FlatSpec, and that compiler error results.

So I'll just need to be patient for when you trunk code gets released.  Right now I can't use nightlies cause it won't jive with ScalaIDE.

Bill Venners

unread,
May 2, 2013, 6:41:36 PM5/2/13
to scalate...@googlegroups.com
Hi Andrew,

Odd. OK. I'll try it later today with 2.0.M5b and see if there's another path.

Bill
Reply all
Reply to author
Forward
0 new messages