ZIO Test: What, Why, and How?
Functional World meetup is a global event aimed at improving learning and knowledge sharing among developers with a passion for functional programming. This year’s edition was focused on the ZIO Scala library. In that vein, we invited developer and ZIO enthusiast Marcin Krykowski to speak about the functional power of ZIO Tests.
Marcin previously worked as a developer at Scalac. He is a poet at heart and believes that Scala and ZIO enable poetry through code. In addition, he recognizes the immense value of approaching software development problems from a beginner’s perspective.
With that philosophy in mind, Marcin delivered an entry-level talk that’s accessible even to someone who has never encountered the ZIO Test framework before.
His presentation is an “ultimate guide” to the ZIO Test framework. Marcin wanted to ensure that after finishing his talk, watchers would be ready for their journey with ZIO Test, meaning they would be able to import a project and run some initial tests.
Answering Common Questions About ZIO Test
Marcin covered many aspects of the ZIO test framework and answered several important questions:
- How do I start with ZIO Test?
- How do I write my first tests?
- How can I improve tests with ZIOTest?
- How do I use test effects with ZIO Test?
- How can I make tests more readable?
- How will my codebase benefit from ZIO Test?
With all that in mind, let’s dive into Macin’s presentation. He began by exploring some of the benefits of the ZIO Test compared to other frameworks.
What Makes ZIO Test Better Than Other Frameworks?
You probably already have a set of preferred testing frameworks for use with your projects. So why should you invest time and effort in familiarizing yourself with a new one?
Marcin believes that employing ZIO Test is worthwhile because many frameworks suffer from the following problems:
- Leaking resources: Some frameworks cause locking, which prevents other components from using a shared resource. A test may have acquired a resource and returned a result, but the resource may still be locked.
- Futures: Some test frameworks use futures as the main test effects.
- Multiple versions and platforms: If you’re coding using Scala, you shouldn’t use it as your main effect. This is because you likely work with various versions and platforms, such as Scala and ScalaJS. Rewriting specific tests for each platform wastes time. Instead, tests should be portable and work with more than one version.
- Dependencies on other services: You may have to fetch code from different parts of the project to make tests work.
- Concurrency: ZIO Test allows multiple tasks to run at the same time.
All of the issues described above, while not insurmountable, slow down the testing process. This costs time and money and affects client delivery. On the other hand, ZIO Test offers a range of functionalities that remedy these problems:
- Functional effect systems
- Referential transparency
- Resource safety
- Environment type
After outlining the benefits of the ZIO Test, Marcin delved into the specifics of setting up and running tests.
How to Set Up a ZIO Test
To set up a ZIO test, first import the library dependency, as seen in the example below.
Marcin primarily uses the SBT version of ZIO Test. Originally known as “Simple Build Tool” and now shortened to its initials, SBT is a build tool specifically for Scala and Java.
Once ZIO Test has been imported, it’s ready to use. The image above shows an example of a typical first test.
As a slight disclaimer, Marcin pointed out that he created the example above as a rough guide. It doesn’t include specific requirements like environment or error type. And different ZIO versions don’t always function in the same way.
How to Write a Test
To write a test, begin by defining the suite that contains other tests. After doing this, make the assertion. In the example below, the assertion tests if the result equals the string “Hello, this is Paul.”
Some testing frameworks use futures as effects. Futures are values that stand in for the output of an asynchronous operation.
Many developers employ the method “unsafeRunSnyc()” in a project when using other testing frameworks. This is a common technique that allows them to describe effects while also running them.
However, it is also resource-intensive. Moreover, developers can run into setbacks, like a behaviour being described but not running because they forgot to use “unsafeRunSync()”. Deadlocks also pose a problem. They occur when two resources overlap in their attempts to use different processes, causing both to stall.
Using ZIO, which has an awareness of effects, helps avoid these setbacks. The result is a “cleaner” and more efficient testing process. In the example above, the method “testM” is aware of the effects and able to run them within its scope.
ZIO Test also provides default implementations of all types of services, such as “clock” or “random,” within the environment. This helps developers to make complex calculations quickly, such as computations related to time passage.
Features of ZIO Test
After providing a brief overview of how to set up a test, Marcin turned his attention to specific features. He explained how they can be used by programmers and the different benefits they provide.
You may have a very expensive layer or resource you want to create once and then reuse many times throughout your codebase. To make an effectful test aware of this, simply provide a custom layer. The implementation of the layer shown below informs your test on how to acquire, when to release, and how to handle the resource.
If you have an expensive layer or resource for sharing between different files and packages, ZIO Test can handle that. Create the resource itself and define all the details—how to create, acquire, and release the resource.
Using the “provideLayerShared()” method that comes with ZIO Test makes things much more manageable. And it’s a solution that’s fully composable.
In addition, resources can be acquired once and released after a process is completed. This overcomes several problems. For example, if a test results in either a pass or fail but a resource hasn’t been used or released.
You can use the same layer in other places in the codebase. This is a great advantage for large organizations because only one instance of coding is required. The layer can be reused whenever needed.
Zio Test offers an excellent package of generators. Generators are helpful if you require property-based testing in your codebase. Easily generate data types like strings and IDs. It’s even possible to combine them into custom case classes, depending on your needs.
Property-based testing with ZIO Test will result in code snippet similar to the sample below.
Use generators to check whether the class or objects have been created correctly. Do they contain concrete values? Or Is the password the same length before and after hashing? ZIO Test also offers keywords to check values such as “name.”
Aspects are another valuable feature of ZIO Test. Among other outcomes, you can achieve the following aspects:
- Make tests flaky or nonFlaky.
- Timeout tests.
- Ignore tests.
- Make tests platform-specific, for example, using jvmOnly.
- Execute an effect after the test is finished.
Using aspects is straightforward. Simply annotate your test or suite with pre-given annotations. You can choose as many annotations as you want and chain them together.
Assertions enable you to test part of your code and receive a boolean result. You can use assertions to check that you are getting the value you expect.
ZIO Test allows for a flexible coding style. Marcin believes that programmers can be poets. The assertion pictured above is an example of “poetic” coding.
Alternatively, opt for the “assertEqual()” method that compares two values (instead of learning a new domain-specific language (DSL)). However, you will need to stay up to date with methods if you plan to use them.
Exception testing is common in Scala. To include an exception, use “ZIO.fail()”. The expected failure message and the resulting failure message can be compared.
Marcin believes that the code pictured above, while a helpful example, doesn’t fully express the true benefits of exception testing with ZIO Test. The real value is seen when individual iterations are extended to a larger codebase with many different exceptions.
Marcin likes to have clear reports outlining the results of every test he conducts. A record that identifies which parts of a program have either failed or passed provides crucial insights and maintains an efficient development flow.
When all of the tests pass, there’s no problem. However, when some of the code fails, a detailed and comprehensive report of what went wrong is invaluable.
Marcin used several references to help form his talk:
- “Get started with ZIO Test” by Wiem Zine Elabidine
- Adam Fraser’s talks on ZIO Test. “0 to 100 with ZIO Test” talk.
- Pavels Sisojev’s article “Effective testing with ZIO Test”
- ZIO Test documentation and zio.dev
The Last Word on ZIO Tests
ZIO Test has a lot to offer. It is well organized, open-source, and easy to read. The interop package also enables transcription from one effect to another. What’s more, the ZIO Test community is constantly growing. It’s an easy framework for developers to learn and there are plenty of resources to help educate in-house teams.
Marcin urges everyone to explore and have fun working with ZIO Test because. He promises that it will be an enriching experience for developers.
If your company is looking for a friendly team of software developers with the expertise and experience needed to streamline your development workflow, Scalac is here to help. Get in touch today.