Exit e-book
Show all chapters
14
Writing the tests
14. 
Writing the tests

Sign up to our Newsletter

Signing up to our newsletter allows you to read all our ebooks.

I agree to receive marketing communication from Scalac.
You can unsubscribe from these communications at any time. For more information on how to unsubscribe, view our Privacy Policy.

Mastering Modularity in ZIO with Zlayer
14

Writing the tests

As we have successfully implemented the TicTacToe application using ZLayers, let’s write the application’s tests now. We’ll cover just some of them here, and of course you can see the complete tests in the jorge-vasquez-2301/zio-zlayer-tictactoe repository.

Writing GameCommandParserSpec

Here’s the test suite for GameCommandParser:


object GameCommandParserSpec extends DefaultRunnableSpec {
  def spec =
    suite("GameCommandParser")(
      suite("parse")(
        testM("menu returns Menu command") {
          val result = GameCommandParser.parse("menu").either
          assertM(result)(isRight(equalTo(GameCommand.Menu)))
        },
        testM("number in range 1-9 returns Put command") {
          val results = ZIO.foreach(1 to 9) { n =>
            for {
              result        <- GameCommandParser.parse(s"$n").either
              expectedField <- ZIO.fromOption(Field.make(n))
            } yield assert(result)(isRight(equalTo(GameCommand.Put(expectedField))))
          }
          results.flatMap(results => ZIO.fromOption(results.reduceOption(_ && _)))
        },
        testM("invalid command returns error") {
          checkM(invalidCommandsGen) { input =>
            val result = GameCommandParser.parse(input).either
            assertM(result)(isLeft(equalTo(ParseError)))
          }
        }
      )
    ).provideCustomLayer(GameCommandParser.live)

  private val validCommands      = List(1 to 9)
  private val invalidCommandsGen = Gen.anyString.filter(!validCommands.contains(_))
}

As you can see, all tests depend on the GameCommandParser module, then we need to provide it so zio-test is able to run the tests. So, we can provide the GameCommandParser live implementation to the whole suite by using Spec#provideCustomLayer. This method provides each test with that part of the environment that does not belong to the standard TestEnvironment, leaving a spec that only depends on it.

Writing TerminalSpec

Let’s take a look to the spec:


object TerminalSpec extends DefaultRunnableSpec {
 def spec = suite("Terminal")(
   testM("getUserInput delegates to Console") {
     checkM(Gen.anyString) { input =>
       val consoleMock: ULayer[Console] = MockConsole.GetStrLn(value(input))
       val env: ULayer[Terminal]        = consoleMock >>> Terminal.live
       val result                       = Terminal.getUserInput.provideLayer(env)
       assertM(result)(equalTo(input))
     }
   },
   testM("display delegates to Console") {
     checkM(Gen.anyString) { frame =>
       val consoleMock: ULayer[Console] =
         MockConsole.PutStr(equalTo(Terminal.ansiClearScreen), unit) ++
           MockConsole.PutStrLn(equalTo(frame), unit)
       val env: ULayer[Terminal] = consoleMock >>> Terminal.live
       val result                = Terminal.display(frame).provideLayer(env)
       assertM(result)(isUnit)
     }
   }
 )
}

Some important things worth noting:

Each test needs a Terminal environment to run, and Terminal itself depends on the Console module. So we create a consoleMock, using the MockConsole that zio-test provides us with:

val consoleMock: ULayer[Console] = MockConsole.GetStrLn(value(input))

In the above line, we are stating that when calling Console.getStrLn, it should return a value equal to input. And also, there’s something interesting: If we take a closer look to this expression:

MockConsole.GetStrLn(value(input))

It returns a value of type Expectation[Console], but we are storing it as a ULayer[Console], and there are no compilation errors… The reason is that ZIO provides an implicit function Expectation#toLayer, which converts an Expectation[R] to a ULayer[R].

  • Because mocks can be defined as ZLayers, we can easily compose them with other ZLayers, using horizontal and vertical composition! For example, we are using the vertical composition here:
val env: ULayer[Terminal] = consoleMock >>> Terminal.live


  • For providing the environment for each test, we are using ZIO#provideLayer. This means that you can provide an environment separately to each test, or you can provide an environment to a whole suite like we did for GameCommandParserSpec.
PREVIOUS
Chapter
13
NEXT
Chapter
15