Last active
May 25, 2024 10:18
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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