Expectations (assertions)

Expectations are pure, composable values. This forces developers to separate the test's checks from the scenario, which is generally cleaner/clearer.

The easiest way to construct expectactions is to call the expect macro. The clue function can be used to investigate failures.

expect(clue(List(1, 2, 3).size) == 4)
Error message produced by the expect macro

TL;DR

Example suite

import weaver._
import cats.effect.IO

object ExpectationsSuite extends SimpleIOSuite {

  def test(a: Int) = a + 5

  pureTest("Simple expectations (success)") {
    val z = 15

    expect(test(z) < z + 6)
  }

  pureTest("Simple expectations (failure)") {
    val z = 15

    expect(clue(test(z)) > z + 6)
  }

  pureTest("And/Or composition (success)") {
    expect(1 != 2) and expect(2 != 1) or expect(2 != 3)
  }

  pureTest("And/Or composition (failure)") {
    (expect(1 != clue(2)) and expect(2 < clue(1))) or expect(2 > clue(3))
  }

  pureTest("Varargs composition (success)") {
    // expect(1 + 1 < 3) && expect(2 + 2 < 5) && expect(4 * 2 < 9)
    expect.all(1 + 1 < 3, 2 + 2 < 5, 4 * 2 < 9)
  }

  pureTest("Varargs composition (failure)") {
    // expect(1 + 1 < 3) && expect(clue(2 + 2) > 5) && expect(4 * 2 < 9)
    expect.all(1 + 1 < 3, clue(2 + 2) > 5, 4 * 2 < 9)
  }

  pureTest("Working with collections (success)") {
    forEach(List(1, 2, 3))(i => expect(i < 5)) and
      forEach(Option("hello"))(msg => expect.eql("hello", msg)) and
      exists(List("a", "b", "c"))(i => expect.eql("c", i)) and
      exists(Vector(true, true, false))(i => expect.eql(false, i))
  }

  pureTest("Working with collections (failure 1)") {
    forEach(Vector("hello", "world"))(msg => expect.eql("hello", msg))
  }

  pureTest("Working with collections (failure 2)") {
    exists(Option(39))(i => expect(clue(i) > 50))
  }

  import cats.Eq
  case class Test(d: Double)

  implicit val eqTest: Eq[Test] = Eq.by[Test, Double](_.d)

  pureTest("Strict equality (success)") {
    expect.eql("hello", "hello") and
      expect.eql(List(1, 2, 3), List(1, 2, 3)) and
      expect.eql(Test(25.0), Test(25.0))
  }

  pureTest("Strict equality (failure 1)") {
    expect.eql("hello", "world")
  }

  pureTest("Strict equality (failure 2)") {
    expect.eql(List(1, 2, 3), List(1, 19, 3))
  }

  pureTest("Strict equality (failure 3)") {
    expect.eql(Test(25.0), Test(50.0))
  }

  // Note that we don't have an instance of Eq[Hello]
  // anywhere in scope
  class Hello(val d: Double) {
    override def toString = s"Hello to $d"

    override def equals(other: Any) =
      if(other != null && other.isInstanceOf[Hello])
        other.asInstanceOf[Hello].d == this.d
      else
        false
  }

  pureTest("Relaxed equality comparison (success)") {
    expect.same(new Hello(25.0), new Hello(25.0))
  }

  pureTest("Relaxed equality comparison (failure)") {
    expect.same(new Hello(25.0), new Hello(50.0))
  }

  pureTest("Non macro-based expectations") {
    val condition : Boolean = false
    if (condition) success else failure("Condition failed")
  }

  test("Failing fast expectations") {
    for {
      h <- IO.pure("hello")
      _ <- expect(clue(h).isEmpty).failFast
    } yield success
  }

  test("Failing fast match") {
    for {
      h <- IO.pure(Some(4))
      x <- matchOrFailFast[IO](h) {
        case Some(v) => v
      }
      g <- IO.pure(Option.empty[Int])
      y <- matchOrFailFast[IO](g) {
        case Some(v) => v
      }
    } yield expect.eql(x, y)
  }
}

Example suite report

ExpectationsSuite
+ Simple expectations (success) 0ms
+ And/Or composition (success) 5ms
+ Varargs composition (success) 4ms
+ Working with collections (success) 13ms
+ Strict equality (success) 9ms
+ Relaxed equality comparison (success) 0ms
- Simple expectations (failure) 1ms
- And/Or composition (failure) 5ms
- Varargs composition (failure) 1ms
- Working with collections (failure 1) 8ms
- Working with collections (failure 2) 4ms
- Strict equality (failure 1) 0ms
- Strict equality (failure 2) 2ms
- Strict equality (failure 3) 6ms
- Relaxed equality comparison (failure) 4ms
- Non macro-based expectations 0ms
- Failing fast expectations 8ms
- Failing fast match 11ms

*************FAILURES*************
ExpectationsSuite
- Simple expectations (failure) 1ms
  assertion failed

  expect(clue(test(z)) > z + 6)

  Clues {
    test(z): Int = 20
  }

  expectations.md#L210
      expect(clue(test(z)) > z + 6)
            ^
- And/Or composition (failure) 5ms
 [0] assertion failed
 [0] 
 [0] expect(2 < clue(1))
 [0] 
 [0] Clues {
 [0]   1: Int = 1
 [0] }
 [0] 
 [0] expectations.md#L218
 [0]     (expect(1 != clue(2)) and expect(2 < clue(1))) or expect(2 > clue(3))
 [0]                                     ^


 [1] assertion failed
 [1] 
 [1] expect(2 > clue(3))
 [1] 
 [1] Clues {
 [1]   3: Int = 3
 [1] }
 [1] 
 [1] expectations.md#L218
 [1]     (expect(1 != clue(2)) and expect(2 < clue(1))) or expect(2 > clue(3))
 [1]                                                             ^
- Varargs composition (failure) 1ms
  assertion failed

  clue(2 + 2) > 5

  Clues {
    2 + 2: Int = 4
  }

  expectations.md#L228
      expect.all(1 + 1 < 3, clue(2 + 2) > 5, 4 * 2 < 9)
                ^
- Working with collections (failure 1) 8ms
  Values not equal:

  in expect.eql(
- expected, + found)
  -hello
  +world

  expectations.md#L239
      forEach(Vector("hello", "world"))(msg => expect.eql("hello", msg))
                                                         ^
- Working with collections (failure 2) 4ms
  assertion failed

  expect(clue(i) > 50)

  Clues {
    i: Int = 39
  }

  expectations.md#L243
      exists(Option(39))(i => expect(clue(i) > 50))
                                    ^
- Strict equality (failure 1) 0ms
  Values not equal:

  in expect.eql(
- expected, + found)
  -hello
  +world

  expectations.md#L258
      expect.eql("hello", "world")
                ^
- Strict equality (failure 2) 2ms
  Values not equal:

  in expect.eql(
- expected, + found)
  -List(1, 2, 3)
  +List(1, 19, 3)

  expectations.md#L262
      expect.eql(List(1, 2, 3), List(1, 19, 3))
                ^
- Strict equality (failure 3) 6ms
  Values not equal:

  in expect.eql(
- expected, + found)
   Test(
  
- d = 25.0
  + d = 50.0
    )

  expectations.md#L266
      expect.eql(Test(25.0), Test(50.0))
                ^
- Relaxed equality comparison (failure) 4ms
  Values not equal:

  in expect.same(
- expected, + found)
  -Hello to 25.0
  +Hello to 50.0

  expectations.md#L286
      expect.same(new Hello(25.0), new Hello(50.0))
                 ^
- Non macro-based expectations 0ms
  Condition failed

  expectations.md#L291
      if (condition) success else failure("Condition failed")
                                         ^
- Failing fast expectations 8ms
  assertion failed

  expect(clue(h).isEmpty)

  Clues {
    h: String = hello
  }

  expectations.md#L297
        _ <- expect(clue(h).isEmpty).failFast
                   ^
- Failing fast match 11ms
  Pattern did not match, got: None

  expectations.md#L308
        y <- matchOrFailFast[IO](g) {
                                    ^
Total 18, Failed 12, Passed 6, Ignored 0