ZIO has the concept of Layers. These allow you to inject requirements to your ZIO monad (the R in ZIO[R, E, A]). The nice thing about them is that they make testing easier.
Say, I have an application with an intialisation piece and a data flow piece. My code would look like this:
import zio.{Has, Task, ZIO}
type Init = Has[Init.Service]
object Init {
trait Service {
def initializeWith(configFile: String,
session: SparkSession,
fs: FileSystem): ZIO[Any, Throwable, Settings]
}
...
type Flow = Has[Flow.Service]
object Flow {
trait Service {
def flow(s: Settings,
paths: Filenames,
session: SparkSession,
fs: FileSystem): Task[Results[String]]
}
Now, the data structure I want to test is something like a ZIO[Init with Flow, Throwable, A] and the code that creates it is probably some large and complicated for-comprehension that's begging for a test.
In production, the code to "run" it would look like:
val prodInitialization: ULayer[Init] = ZLayer.succeed(InitProd)
val prodDataFlow: ULayer[Flow] = ZLayer.succeed(FlowProd)
...
zio.provideLayer(prodInitialization ++ prodDataFlow)
where InitProd and FlowProd are my production objects that implement Init and Flow.
My test code, however, look like:
import zio.test.Assertion._
import zio.test.environment.TestEnvironment
import zio.test.junit.JUnitRunnableSpec
import zio.test.{ZSpec, assertM, suite, testM}
import zio.{Layer, Task, ZIO, ZLayer}
class InitFlowSpec extends JUnitRunnableSpec {
def unhappyPathInitLayer(e: Exception): Layer[Nothing, Init] = ZLayer.succeed(
new Init.Service {
override def initializeWith(configFile: String, session: SparkSession, fs: FileSystem) = ZIO.fail(e)
}
)
...
val error = new Exception("Boo")
val withNoErrors: Results[String] = Map.empty
override def spec: ZSpec[TestEnvironment, Any] = suite("Orchestration")(
testM("returns error if initialisation fails"){
val layers = unhappyPathInitLayer(error) ++ flowLayer(withNoErrors)
val result = zio.provideLayer(layers)
assertM(result.run)(fails(equalTo(error)))
}
)
Et voila, we have a mocking framework for the requirement channel of a ZIO monad.
No comments:
Post a Comment