cats-scalatest

CI codecov.io scaladoc

Scalatest bindings for cats. Inspired by scalaz-scalatest.

Apache 2.0 licensed.

cats-scalatest is a Typelevel project. This means we embrace pure, typeful, functional programming, and provide a safe and friendly environment for teaching, learning, and contributing as described in the Scala Code of Conduct.

Setup

We currently crossbuild for Scala 2.12 and 2.13. Prior to 3.0.4 we cross built for 2.11 as well.

Because cats is such a young project the versioning is not quite following the semantic versioning guidelines (yet). Use the below table to understand which version of the cats-scalatest library you need.

Cats-Scalatest Version Cats Version Scalatest Version
1.0.1 0.2.0 2.2.4
1.1.0 0.4.0 2.2.4
1.1.2 0.4.1 2.2.4
1.3.0 0.6.{0,1} 2.2.6
1.4.0 0.7.0 2.2.6
1.5.0 0.7.2 2.2.6
2.0.0 0.7.2 3.0.0
2.1.0 0.8.0 3.0.0
2.1.1 0.8.1 3.0.0
2.2.0 0.9.0 3.0.0
2.3.0 1.0.0-MF 3.0.0
2.3.1 1.0.1 3.0.0
2.4.0 1.5.0 3.0.5
3.0.0 2.0.0 3.0.8
3.0.4 2.0.0 3.1.0
3.0.5 2.1.0 3.1.0
3.1.1 2.1.1 3.2.3

To include this in your project, add the dependency:

//For cats 2.1.0 and scalatest 3.1, see above chart for others.
libraryDependencies += "com.ironcorelabs" %% "cats-scalatest" % "3.0.5" % "test"

What does this provide?

Matchers & Helpers are presently offered for testing of the following cats concepts:

  • Either
  • Validated

Usage

There are two ways to use the provided matchers:

You can mix them in:

class MySillyWalkSpec extends FlatSpec with Matchers with EitherMatchers {
  // ...
}

This makes the matchers in EitherMatchers available inside the scope of your test.

You can also import explicitly from a provided object:

import cats.scalatest.EitherMatchers

class MySillyWalkSpec extends FlatSpec with Matchers {
  import EitherMatchers._
  // ...
}

Also brings the matchers into scope.

And now, the matchers themselves.

Either Matchers

EitherMatchers supplies the following methods:

beLeft[E](element: E)
left[E]
beRight[T](element: T)
right[T]

Specific Element Matchers

The matchers that begin with a be prefix are for matching a specific element inside of the Either.

Something like the following:

val s = "Hello World"
val valueInRight = Right(s)

//This passes
valueInRight should beRight(s)

//This fails with the following message:
//Right(Hello World) did not contain an Right element matching 'goodbye'.
valueInRight should beRight("goodbye")

The matchers work the same for beLeft.

Right and Left Matchers

The left and right matchers are for checking to see if the Either is a right or left without caring what's inside.

  //This passes
  Left("uh oh") should be(left)

  //This fails with the following message:
  //Left(uh oh) was not an Right, but should have been.
  Left("uh oh") should be(right)

Validated Matchers

cats.data.Validated also has matchers similar to the ones described above.

def beInvalid[E](element: E)
def invalid[E]
def valid[T]
def beValid[T](element: T)

I won't repeat how they're used here. Validated does have some additional matchers though which allows you to describe values that are in the Invalid if you're using ValidatedNel.

The first matcher is haveInvalid and can be used like this:

val validatedNelValue: ValidatedNel[String, Int] = Invalid(NonEmptyList("error1", "error2"))

//The following works fine:
validatedNelValue should haveInvalid("error1")

//But you can also combine them with the and word to match multiple values:
validateNelValue should (haveInvalid("error1") and haveInvalid("error2"))

The second matcher is haveAnInvalid and can be used like this:

val validatedNelValue: ValidatedNel[Exception, Int] = Invalid(NonEmptyList(new ArrayIndexOutOfBoundsException, new NoSuchElementException))

//The following works fine:
validatedNelValue should haveAnInvalid[NoSuchElementException]
validatedNelValue shouldNot haveAnInvalid[NumberFormatException]

//But you can also combine them with the and word to match multiple values:
validateNelValue should (haveAnInvalid[ArrayIndexOutOfBoundsException] and haveAnInvalid[NoSuchElementException])

Values Helpers

A very common test idiom is to want to assert the Either is a Left or a Right and then extract the value. For this we supply EitherValues. This can be mixed into your test or imported as an object just like the matchers above, but instead of providing Matchers it instead adds value and leftValue as syntax to the Either type.

val x = Right("hello")
//Passes!
x.value shouldBe "hello"

//Fails with the following message:
//    'Hello' is Right, expected Left.
x.leftValue shouldBe "hello"

The same is true for the Validated. If you import or mixin ValidatedValues you'll be able to call .value to extract Valid and .invalidValue to extract the Invalid side.

Documentation and Support

Contributors

Idea ported from scalaz-scalatest, which is primarily written by Brendan McAdams.