Search code examples
scalascalatest

How can I define `LoneElement` and `Emptiness` for composed collections?


I have a case class which is a simple wrapper around a collection:

final case class Foo(foos: Seq[Int])

and in my tests, I'd like to make assertions about the emptiness and lone elements of Foo#foos. I'm currently circumventing the problem by accessing Foo#foos directly:

foo.foos shouldBe empty

or

foo.foos.loneElement should===(1)

This works, but breaks encapsulation.

I've looked through the scalatest docs, but haven't found a way to define these operators outside of a companion.

How can I define them? Bonus points for inlining them in the tests.


Solution

  • Define some implicits to deal with Foo (in the test-directory, not in the main source code tree):

    import org.scalatest.enablers.Emptiness
    
    trait FooImplicits {
    
      case class FooLoneElementWrapper(wrapped: Foo) {
        def loneElement: Int = {
          assert(wrapped.foos.size == 1)
          wrapped.foos(0)
        }
      }
      implicit def fooCanHazLoneElement(foo: Foo) = FooLoneElementWrapper(foo)
      implicit object FooEmptiness extends Emptiness[Foo] {
        def isEmpty(foo: Foo) = foo.foos.isEmpty
      }
    
    }
    

    Now simply mixin the trait FooImplicits into the FlatSpec where you would like to use it:

    import org.scalatest._
    
    class FooSpec extends FlatSpec with Matchers with FooImplicits {
    
      "Foo" should "be empty when it's empty" in {
        Foo(Seq()) shouldBe empty
      }
    
      it should "have loneElements" in {
        val x = Foo(Seq(1))
        x.loneElement should equal (1)
      }
    }
    

    Profit:

    [info] Foo
    [info] - should be empty when it's empty
    [info] - should have loneElements
    

    Notice that the FooImplicits can be defined in the same package as Foo in the main tree, even though it is in a completely different test-source-code tree. This way, it can access package-visible members of Foo.