Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active May 25, 2024 10:18
Show Gist options
  • Save dacr/c164fcf65bbedb8975cdc97a51e530bb to your computer and use it in GitHub Desktop.
Save dacr/c164fcf65bbedb8975cdc97a51e530bb to your computer and use it in GitHub Desktop.
ZIO learning - zio tests cheat sheet as tests / published by https://github.com/dacr/code-examples-manager #372e9d7a-34ab-4130-ab39-f5d060b0a2f7/7e3057209f80f5a63798c3669833c877b1d1a45e
// summary : ZIO learning - zio tests cheat sheet as tests
// keywords : scala, scalacli, zio, ziotest, @testable, cheatsheet
// publish : gist
// authors : David Crosson
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2)
// id : 372e9d7a-34ab-4130-ab39-f5d060b0a2f7
// created-on : 2021-11-13T10:01:55+01:00
// managed-by : https://github.com/dacr/code-examples-manager
// run-with : scala-cli $file
// ---------------------
//> using scala "3.4.2"
//> using dep "dev.zio::zio-test:2.0.13"
//> using objectWrapper
// ---------------------
/*
with some parts inspired from https://www.youtube.com/watch?v=PJTn33Qj1nc&ab_channel=Ziverge
*/
import zio.*
import zio.Schedule.{exponential, recurs}
import zio.test.*
import zio.test.Assertion.*
import zio.test.TestAspect.*
import java.io.{File, FileInputStream, FileNotFoundException}
import java.util.UUID
object ThingsToTest {
val trueSuccess: UIO[Boolean] = ZIO.succeed(true)
val falseSuccess: UIO[Boolean] = ZIO.succeed(false)
val itFails: IO[String, Nothing] = ZIO.fail("BADLY")
val theResponse: UIO[Int] = ZIO.succeed(42)
val stringValue: UIO[String] = ZIO.succeed("This is the answer to everything")
val primesList: UIO[List[Int]] = ZIO.succeed(List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29))
val peopleMap: UIO[Map[String, String]] = ZIO.succeed(Map("doe" -> "john", "sagan" -> "carl"))
val notFoundFile: Task[FileInputStream] = ZIO.attempt(new FileInputStream(s"truc-${UUID.randomUUID()}.tmp"))
val foundFile: Task[FileInputStream] = ZIO.attempt(new FileInputStream(File.createTempFile("truc", ".tmp")))
def tooLong(duration: Duration) = ZIO.attemptBlocking(Thread.sleep(duration.toMillis()))
}
object ClassicTests extends ZIOSpecDefault {
val integrationTestAspects =
unix
>>> jvmOnly
>>> ifEnvSet("CI_BUILD")
>>> ifEnv("DISABLE_TEST")(_.trim.toLowerCase != "true")
def spec = suite("learning zio-test through tests")(
// --------------------------------------------------------------------------------------
test("always valid test") {
assert(42)(anything)
},
// --------------------------------------------------------------------------------------
test("better always valid test") {
assertTrue(true)
},
// --------------------------------------------------------------------------------------
test("of course we can test anything, not at all required to use ZIO") {
assertTrue {
def add(x: Int, y: Int) = x + y
add(40, 2) == 42
}
},
// --------------------------------------------------------------------------------------
test("when we expect a test to always fail") {
assert(42)(nothing)
} @@ failing,
// --------------------------------------------------------------------------------------
test("dealing with long running test") {
for _ <- ThingsToTest.tooLong(300.seconds)
yield assertTrue(true)
} @@ timeout(100.millis)
@@ failing,
// --------------------------------------------------------------------------------------
test("dealing with a flaky test") {
val rng = java.util.Random()
assertTrue(42 / rng.nextInt(2) > 0)
} @@ flaky(10),
// --------------------------------------------------------------------------------------
test("dealing with a flaky test using an advanced retry strategy") {
val rng = java.util.Random()
assertTrue(42 / rng.nextInt(2) > 0)
} @@ retry(
Schedule.exponential(100.millis, 2).jittered && Schedule.recurs(4)
),
// --------------------------------------------------------------------------------------
test("make a test conditional") {
assertTrue(42 > 0)
} @@ unix
@@ jvmOnly
@@ ifEnvSet("CI_BUILD")
@@ ifEnv("DISABLE_TEST")(_.trim.toLowerCase != "true"),
// --------------------------------------------------------------------------------------
test("my aspects configuration reuse") {
assertTrue(42 > 0)
} @@ integrationTestAspects, // Too avoid repetition
// --------------------------------------------------------------------------------------
test("don't forget to chain your assertions together 1/2") {
assertTrue(43 == 42) // of course it is ignored !
assertTrue(42 == 42)
},
// --------------------------------------------------------------------------------------
test("don't forget to chain your assertions together 2/2") {
assertTrue(43 == 42) &&
assertTrue(42 == 42)
} @@ failing,
// --------------------------------------------------------------------------------------
test("ignore a test") {
assert(42)(anything)
} @@ ignore,
// --------------------------------------------------------------------------------------
test("add diagnostic information during the test if an assertion fails") {
assert(42)(nothing ?? "not satisfying" ?? "not equal to 42" ?? "this is diagnostic data to help you")
} @@ ignore,
// --------------------------------------------------------------------------------------
test("add diagnostic information during the test if an assertion fails") {
assertTrue(42 == 43).label("Something goes wrong, this is diagnostic information to help you")
} @@ ignore,
// --------------------------------------------------------------------------------------
test("several assertion approaches, the effectful one : assertM(effect)(...)") {
assertZIO(ThingsToTest.trueSuccess)(
equalTo(true) && isTrue && not(isFalse)
)
},
// --------------------------------------------------------------------------------------
test("several assertion approaches, the standard one : assert(value)(...)") {
for {
result <- ThingsToTest.trueSuccess
} yield {
assert(result)(equalTo(true)) &&
assert(result)(isTrue) &&
assert(result)(not(isFalse))
}
},
// --------------------------------------------------------------------------------------
test("several assertion approaches, the standard one : assert(value)(...) with more compact style") {
for {
result <- ThingsToTest.trueSuccess
} yield {
assert(result)(
equalTo(true) &&
isTrue &&
not(isFalse)
)
}
},
// --------------------------------------------------------------------------------------
test("several assertion approaches: the simplest one : assertTrue(cond1, ...)") {
for {
result <- ThingsToTest.trueSuccess
} yield {
assertTrue(result == true, result, result != false)
}
},
// --------------------------------------------------------------------------------------
test("test several effects using for comprehension") {
for {
result1 <- ThingsToTest.trueSuccess
result2 <- ThingsToTest.falseSuccess
} yield {
assertTrue(result1, !result2)
}
},
// --------------------------------------------------------------------------------------
test("check numeric values") {
for {
result <- ThingsToTest.theResponse
} yield {
assert(result)(
equalTo(42) &&
isGreaterThan(0) &&
isGreaterThanEqualTo(42) &&
isLessThan(50) &&
isLessThanEqualTo(42) &&
isWithin(40, 42) &&
approximatelyEquals(41, 2) &&
isPositive[Int] &&
nonNegative[Int]
)
}
},
// --------------------------------------------------------------------------------------
test("check string values") {
for {
result <- ThingsToTest.stringValue
} yield {
assert(result)(
isNonEmptyString &&
containsString("the") &&
matchesRegex(".*answer.*") &&
startsWithString("This") &&
endsWithString("thing") &&
hasSizeString(isGreaterThan(10))
)
}
},
// --------------------------------------------------------------------------------------
test("check iterable content") {
for {
result <- ThingsToTest.primesList
} yield {
assert(result)(
isNonEmpty &&
hasFirst(equalTo(2)) &&
hasLast(equalTo(29)) &&
contains(11) &&
hasSameElements(List(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)) &&
hasSameElements(List(23, 29, 2, 3, 5, 7, 11, 13, 17, 19)) &&
equalTo(Iterable(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)) &&
hasSubset(List(5, 7)) &&
hasOneOf(List(1, 5, 10)) &&
hasNoneOf(List(4, 6, 8, 12)) &&
hasSize(equalTo(10)) &&
hasSize(isGreaterThan(2)) &&
exists(equalTo(11)) &&
hasAt(3)(equalTo(7)) &&
isDistinct &&
isNonEmpty &&
isSorted[Int]
)
}
},
// --------------------------------------------------------------------------------------
test("check map content") {
for result <- ThingsToTest.peopleMap
yield assert(result)(hasKey("sagan"))
},
// --------------------------------------------------------------------------------------
test("check errors") {
for result <- ThingsToTest.itFails.exit
yield assert(result)(fails(equalTo("BADLY")))
},
// --------------------------------------------------------------------------------------
test("check exceptions using flip") {
for result <- ThingsToTest.notFoundFile.flip
yield assert(result)(isSubtype[FileNotFoundException](anything))
},
// --------------------------------------------------------------------------------------
test("check exceptions using fails") {
for result <- ThingsToTest.notFoundFile.exit
yield assert(result)(fails(isSubtype[FileNotFoundException](anything)))
},
// --------------------------------------------------------------------------------------
test("check exceptions message content") {
for result <- ThingsToTest.notFoundFile.exit
yield assert(result)(fails(hasMessage(containsString("truc-"))))
},
// --------------------------------------------------------------------------------------
test("check exceptions using failing aspect") {
assertZIO(ThingsToTest.notFoundFile)(anything)
} @@ failing,
// --------------------------------------------------------------------------------------
test("smart assertions first") {
assertTrue(42 == 42)
},
// --------------------------------------------------------------------------------------
test("smart assertions revisited") {
for {
result1 <- ThingsToTest.trueSuccess
result2 <- ThingsToTest.primesList
} yield { // the only drawback of smart assertions is you'll have to repeat the values on which you make your checks
assertTrue(result1) &&
assertTrue(result2.contains(5)) &&
assertTrue(result2.size == 10) ||
assertTrue(
result1,
result2.contains(5),
result2.size == 10
)
}
},
// --------------------------------------------------------------------------------------
suite("suite can be nested...")(
suite("...into an other suite")(
test("some test 1")(
assertTrue(true)
),
test("some test 2")(
assertTrue(true)
)
)
),
// --------------------------------------------------------------------------------------
suite("suite and tests are values") {
val test1 = test("T1")(assertTrue(true))
val test2 = test("T2")(assertTrue(true))
def makeTest(n: Int) = test(s"T$n")(assertTrue(true))
val moreTests = 3.to(10).map(makeTest).toList
suite("inner suite")(test1 :: test2 :: moreTests*)
.mapLabel(label => s"custom test : $label")
},
// --------------------------------------------------------------------------------------
test("resources management during testing") {
assertTrue(true)
// TODO
}
)
}
ClassicTests.main(Array.empty)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment