Integration library between MUnit and ZIO.
Library published for Scala 3, 2.13, 2.12.
libraryDependencies += "org.scalameta" %% "munit" % munitVersion % Test
libraryDependencies += "com.github.poslegm" %% "munit-zio" % munitZIOVersion % TestIf you are using a version of sbt lower than 1.5.0, you will also need to add:
testFrameworks += new TestFramework("munit.Framework")Run tests:
$ sbt
> test
> testOnly com.MySuite
Full MUnit documentation available here.
MUnit integration provided by abstract class ZSuite. It contains testZ
function which runs ZIO effect as MUnit test.
Note:
ZSuitewill throwWrongTestMethodErrorif you passZIOinto plain MUnittestmethod. It prevents non-running effects in tests. Also compiler option-Wvalue-discardis highly recommended for working with effects.
import munit.*
import zio.*
class SimpleZIOSpec extends ZSuite:
testZ("1 + 1 = 2") {
for
a <- ZIO.attempt(1)
b <- ZIO.attempt(1)
yield assertEquals(a + b, 2)
}You can use any of MUnit assertions in ZIO code, but ZSuite contains
helpful ZIO-specific assertions.
Asserts that ZIO[R, E, Boolean] returns true.
testZ("false OR true should be true") {
val effect = ZIO.succeed(false || true)
assertZ(effect)
}Asserts that ZIO[R, E, String] has no difference with expected string. Pretty
prints diff unlike just assertEqualsZ.
testZ("strings are the same") {
val effect = ZIO.succeed("string")
assertNoDiffZ(effect, "string")
}Asserts that ZIO[R, E, A] returns the same result as expected
testZ("values are the same") {
val effect = ZIO.succeed(42)
assertEqualsZ(effect, 42)
}Extension methods for intercept ZIO's failures or defects.
Asserts that ZIO[R, E, Any] should fail with provided exception E.
testZ("effect should fail") {
val effect = ZIO.fail(new IllegalArgumentException("BOOM!"))
effect.interceptFailure[IllegalArgumentException]
}Asserts that ZIO[R, E, Any] should fail with provided exception E and
message message.
testZ("effect should fail with message") {
val effect = ZIO.fail(new IllegalArgumentException("BOOM!"))
effect.interceptFailureMessage[IllegalArgumentException]("BOOM!")
}Asserts that ZIO[R, E, Any] should die with provided exception E.
testZ("effect should die") {
val effect = ZIO.die(new IllegalArgumentException("BOOM!"))
effect.interceptDefect[IllegalArgumentException]
}Resource management in ZSuite based on Scoped and MUnit fixtures.
"Test-local" means that resource will be acquired and released on every
testZ execution. "Suite-local" means that resource will be acquired before
all testZ executions and released after all testZ executions.
Resources from test- and suite-local fixtures can be accessed directly from
testZ.
You can create test-local fixture from Scoped or raw acquire/release effects.
// define fixture with raw acquire/release effects
// `options` contains metadata about current test like its name
val rawZIOFunFixture = ZTestLocalFixture(options => ZIO.succeed(s"acquired ${options.name}")) {
str => putStrLn(s"cleanup [$str]").provideLayer(Console.live)
}
// use it with `testZ` extension method with resource access
rawZIOFunFixture.testZ("allocate resource with ZIO FunFixture") { str => // <- resource
val effect = ZIO.attempt(str.trim)
assertNoDiffZ(effect, "acquired allocate resource with ZIO FunFixture")
}
// similarly for `Scoped`
val ScopedFunFixture = ZTestLocalFixture { options =>
ZIO.acquireRelease(ZIO.succeed(s"acquired ${options.name} with Scoped")) { str =>
printLine(s"cleanup [$str] with Scoped").orDie
}
}
ScopedFunFixture.testZ("allocate resource with Scoped FunFixture") { str =>
val effect = ZIO.attempt(str.trim)
assertNoDiffZ(effect, "acquired allocate resource with Scoped FunFixture with Scoped")
}Suite-local fixture can be created from Scoped and provides synchronous
access to its resource.
// dirty example, don't do it in real code
var state = 0
val fixture = ZSuiteLocalFixture(
"sample",
ZIO.acquireRelease(ZIO.attempt { state += 1; state })(_ => ZIO.attempt { state += 1 }.orDie)
)
// suite-local fixture should be necessarily initialized
override val munitFixtures = Seq(fixture)
test("suite local fixture works") {
val current: Int = fixture() // <- access resource
assertEquals(current, 1)
}You can provide dependencies to your tests with test-local fixture. There is
testZLayered extension method for fixtures with ULayer[R] resource. It just
provides layers into test body, so ZIO[R, E, A] can be converted to ZIO[Any, E, A] with ULayer[R] fixture.
// accessor methods dependent on some `StatefulRepository` and `Service`:
def clean: URIO[Has[StatefulRepository], Unit] = ZIO.service[StatefulRepository].flatMap(_.clean)
def fetch: RIO[Has[Service], Unit] = ZIO.service[Service].flatMap(_.fetch)
def write: RIO[Has[Service], Unit] = ZIO.service[Service].flatMap(_.write)
// ===========
val layersFixture = ZTestLocalFixture { _ =>
// wire layers in `Scoped`'s acquire
ZIO.acquireRelease(ZIO.succeed(StatefulRepository.test >+> Service.test))(layers =>
// graceful release resources after test execution
clean.provideLayer(layers)
)
}
layersFixture.testZLayered("auto provide layer") {
val effect: RIO[Has[Service], Unit] = write *> write *> fetch
assertEqualsZ(effect, 2) // Has[Service] will be provided from fixture
}