How to use Assertions in Testing Zio Programs

data-science-analytics
Reading Time: 2 minutes

In this blog, we’ll talk about Assertions in Testing Zio Programs and why it’s useful for us as programmers and how we can use them.

Zio

Testing

ZIO Test is a zero dependency testing library which makes it easy to test effectual programs.

Zio test works very well using ZIO. It provides the ability to use IOs which will be interpret by its internal runtime implementation. However we can test our programs even if we are not using ZIO.

In ZIO Test, all tests are immutable values and tests are tightly integrated with ZIO, so testing effectual programs is as natural as testing pure ones.

Testing by using Assertions

An assertion is a boolean expression at a specific point. In a program which will be true unless there is a bug in the program. We can define test assertion as an expression, which encapsulates some testable logic specified about a target under test.

To create Assertion[A] object we can use functions defined under zio.test.Assertion. It has already a number of useful assertions predefined like equalToisTruecontainsthrows and more.

What is really useful in assertions is that it behave like boolean values. Also can be composed with operators known from operating on boolean values like and (&&), or (||), negation (negate).

For example, when we working with collections we may want to assert that two collections have the same elements, even if they do not appear in identical order.

We can easily do this using the hasSameElements assertion.

object ExampleSpec extends DefaultRunnableSpec {

  def spec = suite("ExampleSpec")(

    test("hasSameElement") {

     assert(List(1, 1, 2, 3))(hasSameElements(List(3, 2, 1, 1)))

    }

  }

}

The second parameter to hasAt is an Assertion[A] that applies to the third element of that sequence, so I would look for functions that operate on A, of the return type Assertion[A].

I could select equalTo, as it accepts an A as a parameter, allowing me to supply 5.

val x = Vector(0, 1, 2, 3)

test("4th value is equal to 5") {

  assert(x)(hasAt(3)(equalTo(5)))

}

Also if I would prefer to assert that a value is near the number five, with a tolerance of two. This requires a little more knowledge of the type A, so I’ll look for an assertion in the Numeric section. approximatelyEquals is a good fit, as it permits the starting value reference, as well as a tolerance, for any A that is Numeric:

val x = Vector(0, 1, 2, 3)

test("4th value is approximately equal to 5") {

  assert(x)(hasAt(3)(approximatelyEquals(5, 2)))

}

And if we want to assert that a collection of integers has at least one value and that all of the values are greater than or equal to zero. We could try this:

val assert: Assertion[Iterable[Int]]=

  isNonEmpty && forall(nonNegative)

We can also negate assertions using the not assertion. For example, we could
express an expectation that a collection contains at least one duplicate element
like this:

val assertion: Assertion[Iterable[Any]] =

  not(isDistinct)

If we ever get to a point where we don’t care about the specific value.For example we just care that the effect failed and instead we don’t care about how it failed, we can use the anything assertion to express an assertion that is always true.