51

I'm trying to unit-test some Scala that is very collection-heavy. These collections are returned as Iterable[T], so I am interested in the contents of the collection, even if the underlying types differ. This is actually two related problems:

  1. How do I assert that two ordered collections contain the same sequence of elements?
  2. How do I assert that two unordered collections contain the same set of elements?

In summary, I'm looking the Scala-equivalent of NUnit's CollectionAssert.AreEqual (ordered) and CollectionAssert.AreEquivalent (unordered) in ScalaTest:

Set(1, 2) should equal (List(1, 2))          // ordered, pass
Iterable(2, 1) should equal (Iterable(1, 2)) // unordered, pass
Michael Koval
  • 8,207
  • 5
  • 42
  • 53
  • For the unordered case, if you're not worried about a bit of extra memory usage, you could call `.toSet` on the collections you want to compare. – Dylan Sep 15 '11 at 17:48
  • Calling `toSet` is exactly the behavior I want, but it behaves incorrectly if the collection contains duplicate elements. – Michael Koval Sep 15 '11 at 19:35
  • You could possibly use the `groupBy` method, and pass in some identity function as the mapper. – Dylan Sep 15 '11 at 20:12

2 Answers2

108

Meanwhile you can use

Iterable(2, 1) should contain theSameElementsAs Iterable(1, 2)

To test the ordered set you have to convert it to a sequence.

Set(1, 2).toSeq should contain theSameElementsInOrderAs List(1, 2)
Christoph Dittberner
  • 1,366
  • 2
  • 9
  • 12
  • 3
    re: second example. `Set` has no defined order, so the order when converted to a `Seq` is essentially random and you should never have a test whose result is dependent on that order. If you are using an ordered Set like `SortedSet` with `toSeq`, how is `should contain theSameElementsInOrderAs` different to `should equal`, aside from a catchier name? – Luigi Plinge May 14 '15 at 11:22
  • 28
    Is there any way to get `sbt` to give a nicer output than `.... did not contain the same elements as ...`? Specifically, it would be nice if it would tell me what's missing from a large collection. – mattg Jun 05 '15 at 21:28
  • How to use it recursively? Comparing case classes with a collection member is common. – Waldemar Wosiński Nov 19 '19 at 10:53
28

You could try .toSeq for ordered collections and .toSet for unordered, which captures what you want as far as I understand it.

The following passes:

class Temp extends FunSuite with ShouldMatchers {
  test("1")  { Array(1, 2).toSeq should equal (List(1, 2).toSeq) }
  test("2")  { Array(2, 1).toSeq should not equal (List(1, 2).toSeq) }
  test("2b") { Array(2, 1) should not equal (List(1, 2)) }  
  test("3")  { Iterable(2, 1).toSet should equal (Iterable(1, 2).toSet) }
  test("4")  { Iterable(2, 1) should not equal (Iterable(1, 2)) }
}

BTW a Set is not ordered.

edit: To avoid removing duplicate elements, try toSeq.sorted. The following pass:

  test("5")  { Iterable(2, 1).toSeq.sorted should equal (Iterable(1, 2).toSeq.sorted) }
  test("6")  { Iterable(2, 1).toSeq should not equal (Iterable(1, 2).toSeq) }

edit 2: For unordered collections where elements cannot be sorted, you can use this method:

  def sameAs[A](c: Traversable[A], d: Traversable[A]): Boolean = 
    if (c.isEmpty) d.isEmpty
    else {
      val (e, f) = d span (c.head !=)
      if (f.isEmpty) false else sameAs(c.tail, e ++ f.tail)
    }

e.g. (note use of symbols 'a 'b 'c which have no defined ordering)

  test("7")  { assert( sameAs(Iterable(2, 1),    Iterable(1, 2)     )) }
  test("8")  { assert( sameAs(Array('a, 'c, 'b), List('c, 'a, 'b)   )) }
  test("9")  { assert( sameAs("cba",             Set('a', 'b', 'c') )) }

Alternative sameAs implementation:

  def sameAs[A](c: Traversable[A], d: Traversable[A]) = {
    def counts(e: Traversable[A]) = e groupBy identity mapValues (_.size)
    counts(c) == counts(d)
  }
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • Thanks for the answer. This is very close to what I'm looking for, but not exactly: `toSet` eliminates duplicate values, so `Array(1, 1).toSet should not equal (Array(1))` incorrectly fails. From what I can tell, Scala does not have a MultiSet collection. Do you have any suggestions? – Michael Koval Sep 15 '11 at 19:33
  • 1
    @Michael good point, see my edit. If your items aren't ordered (i.e. you can't sort them), it might be a bit trickier – Luigi Plinge Sep 15 '11 at 19:48
  • Defining an ordering isn't ideal, but it's a workable in this situation. Thanks! – Michael Koval Sep 15 '11 at 19:58
  • @MichaelKoval @Luigi Another alternative implementation of the `sameAs` method is `def sameAs[A](c: Traversable[A], d: Traversable[A]) = c.size == d.size && (c.toSeq diff d.toSeq).isEmpty`. – Henry Jan 14 '13 at 09:41
  • 1
    this answer is outdated. The ScalaTest API has been improved. – Christoph Dittberner Apr 16 '15 at 11:09
  • 1
    As @ChristophDittberner already said, ScalaTest 3.0 provides [Matchers to work with containers](http://www.scalatest.org/user_guide/using_matchers#workingWithAggregations). – phw Mar 09 '17 at 13:46