zio test

ZIO Test: What, Why, and How?

zio test

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. 

Marcin covered many aspects of the ZIO test framework and answered several important questions:

Answering Common Questions About ZIO Test

  • 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
  • Interruptibility

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.

scala outsourcing
How to import ZIO Test 

Download the presentation in PDF

val zioVersion = "some_version"

libraryDependencies ++= Seq(
 "dev.zio" %% "zio-test"          % zioVersion % "test",
 "dev.zio" %% "zio-test-sbt"      % zioVersion % "test"
)
trait TestAspect[+R0, -R1, +E0, -E1, +S0, -S1] {
def apply[R >: R0 <: R1, E >: E0 <: E1, S >: S0 <: 
S1](spec: ZSpec[R, E, L, S]): ZSpec[R, E, L, S]
}

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. 

scala outsourcing
Code for a simple test using ZIO Test
object InitialTest extends DefaultRunnableSpec {
 def spec = suite("Example Spec")(
     test("Introduce someone") {
       val result = introduceSomeone("Paul")
       assert(result)(equalTo("Hello, this is Paul"))
     }
)
}

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.”

scala outsourcing
An example of a test as an effect
testM("expected values") {
 for {
   result <- someStream.take(3).runCollect
 } assert(result)(equalTo(someExpectedStream))
}

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.

scala outsourcing
Examples of implementations within the ZIO Test environment
testM("expect call for overloaded method") {
 val app = random.nextInt
 val env = MockRandom.NextInt(value(42))
 val out = app.provideLayer(env)
 assertM(out)(equalTo(42))
}

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. 

Resources

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.

zio test - resources
Example of a resource with a custom layer
 val myLayer: ULayer[MyLayer] = ZLayer.fromManaged { ... }

 def spec = suite("Resources")(
     testM("My Test"){
       ???
     }.provideCustomLayer(myLayer)
 )

Shared Resources

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 - shared resources
An example of shared resources
val myHyperExpensiveResource: ULayer[HyperExpensiveResource] = ZLayer.fromManaged { ... }

 override def spec = suite("WithResourceSpec")(
   testM("test 1") { ... },
   testM("test 2") { ... }
 ).provideLayerShared(myHyperExpensiveResource)

Generators

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. 

zio test - generators
Code for importing generators
import zio.random.Random
import zio.test.{Gen, Sized}
import zio.test.Gen._
object Generators {
 val name: Gen[Random with Sized, Name] = anyString.map(Name.apply)
 val id: Gen[Random with Sized, Id] = anyInt.map(Id.apply)
}

Property-Based Testing

Property-based testing with ZIO Test will result in code snippet similar to the sample below. 

scala outsourcing
Code example for property-based testing using generators
testM("Test Generators") {
       check(Generators.name) { name =>
         assert(name.value)(isNonEmptyString)
       }
     }

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

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

scala outsourcing
Assertions coded using assertions or the “assetEqual()” method
assert(result)(hasLength(equalTo(5))) && assert(result)(isSorted)

or

assertEqual(Option("Paul").get, "Paul")

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

zio test - exeption testing
A code example of exception testing
object ThrowableSpec extends DefaultRunnableSpec {
 def spec = suite("Exception Suite")(
   testM("Example of testing for expected failure") {
     assert(ZIO.fail("fail").run)(fails(equalTo("fail")))
   }
 )
}

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. 

Reporting

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. 

Download the presentation in PDF

References

Marcin used several references to help form his talk:

Additionally, Marcin is available on Twitter, LinkedIn, Github, and email.

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. So learn zio!

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. 

Read also:

Download e-book:

Scalac Case Study Book

Download now

Authors

Daria Karasek
Daria Karasek

Marketing Hero at Scalac. I strongly believe in creating opportunities rather than waiting for them to come. As befits Scalac team member I'm a hard worker, I always try to do the right thing and have a lot of fun! I'm an awesome friend and content writer, in that order. When I'm out of the office, I love to cook delicious Italian food and play board games with my friends. #boardgamegeek

Latest Blogposts

14.03.2024 / By  Dawid Jóźwiak

Implementing cloud VPN solution using AWS, Linux and WireGuard

Implementing cloud VPN solution using AWS, Linux and WireGuard

What is a VPN, and why is it important? A Virtual Private Network, or VPN in short, is a tunnel which handles all the internet data sent and received between Point A (typically an end-user) and Point B (application, server, or another end-user). This is done with security and privacy in mind, because it effectively […]

07.03.2024 / By  Bartosz Puszczyk

Building application with AI: from concept to prototype.

Blogpost About Building an application with the power of AI.

Introduction – Artificial Intelligence in Application Development When a few years ago the technological world was taken over by the blockchain trend, I must admit that I didn’t hop on that train. I couldn’t see the real value that this technology could bring to someone designing application interfaces. However, when the general public got to […]

28.02.2024 / By  Matylda Kamińska

Scalendar March 2024

scalendar march 2024

Event-driven Newsletter In the rapidly evolving world of software development, staying in line with the latest trends, technologies, and community gatherings is crucial for professionals seeking to enhance their skills and network. Scalendar serves as your comprehensive guide to navigate events scheduled worldwide, from specialized Scala conferences in March 2024 to broader gatherings on software […]

software product development

Need a successful project?

Estimate project