From ebb5e56e98b5392e31da79c9e99ac41d6ae97632 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sat, 3 Dec 2022 19:59:56 +0100 Subject: [PATCH 01/12] fixed zio-mock documentation on mocking collaborators compose layers --- docs/ecosystem/officials/zio-mock.md | 96 +++++++++++++++------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index 76e04c46a381..79a712261817 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -123,15 +123,16 @@ object MockEmailService extends Mock[EmailService] { object Send extends Effect[(String, String), String, Unit] val compose: URLayer[Proxy, EmailService] = - ZIO - .service[Proxy] - .map { proxy => - new EmailService { - override def send(to: String, body: String): IO[String, Unit] = - proxy(Send, to, body) + ZLayer.fromZIO( + ZIO + .service[Proxy] + .map { proxy => + new EmailService { + override def send(to: String, body: String): IO[String, Unit] = + proxy(Send, to, body) + } } - } - .toLayer + ) } ``` @@ -145,15 +146,16 @@ object MockUserRepository extends Mock[UserRepository] { object Save extends Effect[User, String, Unit] val compose: URLayer[Proxy, UserRepository] = - ZIO - .service[Proxy] - .map { proxy => - new UserRepository { - override def save(user: User): IO[String, Unit] = - proxy(Save, user) + ZLayer.fromZIO( + ZIO + .service[Proxy] + .map { proxy => + new UserRepository { + override def save(user: User): IO[String, Unit] = + proxy(Save, user) + } } - } - .toLayer + ) } ``` @@ -476,16 +478,18 @@ object MockPolyService extends Mock[PolyService] { // We will learn about the compose layer in the next section val compose: URLayer[Proxy, PolyService] = - ZIO.serviceWithZIO[Proxy] { proxy => - withRuntime[Any].map { rts => - new PolyService { - def polyInput[I: Tag](input: I) = proxy(PolyInput.of[I], input) - def polyError[E: Tag](input: Int) = proxy(PolyError.of[E], input) - def polyOutput[A: Tag](input: Int) = proxy(PolyOutput.of[A], input) - def polyAll[I: Tag, E: Tag, A: Tag](input: I) = proxy(PolyAll.of[I, E, A], input) + ZLayer.fromZIO( + ZIO.serviceWithZIO[Proxy] { proxy => + withRuntime[Any].map { rts => + new PolyService { + def polyInput[I: Tag](input: I) = proxy(PolyInput.of[I], input) + def polyError[E: Tag](input: Int) = proxy(PolyError.of[E], input) + def polyOutput[A: Tag](input: Int) = proxy(PolyOutput.of[A], input) + def polyAll[I: Tag, E: Tag, A: Tag](input: I) = proxy(PolyAll.of[I, E, A], input) + } } } - }.toLayer + ) } ``` @@ -552,23 +556,25 @@ object MockExampleService extends Mock[ExampleService] { object ExampleStream extends Stream[Int, Throwable, String] override val compose: URLayer[Proxy, ExampleService] = - ZIO.serviceWithZIO[Proxy] { proxy => - withRuntime[Any].map { rts => - new ExampleService { - override def exampleEffect(i: Int): Task[String] = - proxy(ExampleEffect, i) - - override def exampleMethod(i: Int): String = - rts.unsafeRunTask(proxy(ExampleMethod, i)) - - override def exampleSink(a: Int): stream.Sink[Throwable, Int, Nothing, List[Int]] = - rts.unsafeRun(proxy(ExampleSink, a)) - - override def exampleStream(a: Int): stream.Stream[Throwable, String] = - rts.unsafeRun(proxy(ExampleStream, a)) + ZLayer.fromZIO( + ZIO.serviceWithZIO[Proxy] { proxy => + withRuntime[Any].map { rts => + new ExampleService { + override def exampleEffect(i: Int): Task[String] = + proxy(ExampleEffect, i) + + override def exampleMethod(i: Int): String = + rts.unsafeRunTask(proxy(ExampleMethod, i)) + + override def exampleSink(a: Int): stream.Sink[Throwable, Int, Nothing, List[Int]] = + rts.unsafeRun(proxy(ExampleSink, a)) + + override def exampleStream(a: Int): stream.Stream[Throwable, String] = + rts.unsafeRun(proxy(ExampleStream, a)) + } } } - }.toLayer + ) } ``` @@ -631,12 +637,14 @@ object AccountObserverMock extends Mock[AccountObserver] { object RunCommand extends Effect[Unit, Nothing, Unit] val compose: URLayer[Proxy, AccountObserver] = - ZIO.service[Proxy].map { proxy => - new AccountObserver { - def processEvent(event: AccountEvent) = proxy(ProcessEvent, event) - def runCommand(): UIO[Unit] = proxy(RunCommand) + ZLayer.fromZIO( + ZIO.service[Proxy].map { proxy => + new AccountObserver { + def processEvent(event: AccountEvent) = proxy(ProcessEvent, event) + def runCommand(): UIO[Unit] = proxy(RunCommand) + } } - }.toLayer + ) } ``` From 2ef38328adac4d63a82d7bdf9c4bb9b83b61a6d9 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sun, 4 Dec 2022 13:29:43 +0100 Subject: [PATCH 02/12] missing proper layer constructor in compose method --- docs/ecosystem/officials/zio-mock.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index 79a712261817..d4f0e7803a65 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -719,16 +719,18 @@ object MockUserService extends Mock[UserService] { object RemoveAll extends Effect[Unit, String, Unit] val compose: URLayer[mock.Proxy, UserService] = - ZIO.service[mock.Proxy] - .map { proxy => - new UserService { - override def insert(user: User): IO[String, Unit] = proxy(Insert, user) - override def remove(id: String): IO[String, Unit] = proxy(Remove, id) - override def recentUsers(n: Int): IO[String, List[User]] = proxy(RecentUsers, n) - override def totalUsers: IO[String, Int] = proxy(TotalUsers) - override def removeAll: IO[String, Unit] = proxy(RemoveAll) + ZLayer.fromZIO( + ZIO.service[mock.Proxy] + .map { proxy => + new UserService { + override def insert(user: User): IO[String, Unit] = proxy(Insert, user) + override def remove(id: String): IO[String, Unit] = proxy(Remove, id) + override def recentUsers(n: Int): IO[String, List[User]] = proxy(RecentUsers, n) + override def totalUsers: IO[String, Int] = proxy(TotalUsers) + override def removeAll: IO[String, Unit] = proxy(RemoveAll) + } } - }.toLayer + ) } ``` From 341bb5c96ba94da3335a06cd16fa373e1c98f3e5 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sun, 4 Dec 2022 13:50:33 +0100 Subject: [PATCH 03/12] `The Problem` mdoc'ed and compiling --- docs/ecosystem/officials/zio-mock.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index d4f0e7803a65..436eca8b8047 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -21,7 +21,7 @@ If the job of the _capability_ is to call on another _capability_, how should we Let's say we have a `Userservice` defined as follows: -```scala +```scala mdoc:silent import zio._ trait UserService { @@ -36,7 +36,7 @@ object UserService { The live implementation of the `UserService` has two collaborators, `EmailService` and `UserRepository`: -```scala +```scala mdoc:nest:silent trait EmailService { def send(to: String, body: String): IO[String, Unit] } @@ -50,7 +50,7 @@ trait UserRepository { Following is how the live version of `UserService` is implemented: -```scala +```scala mdoc:nest:silent case class UserServiceLive(emailService: EmailService, userRepository: UserRepository) extends UserService { override def register(username: String, age: Int, email: String): IO[String, Unit] = if (age < 18) { @@ -67,7 +67,12 @@ case class UserServiceLive(emailService: EmailService, userRepository: UserRepos object UserServiceLive { val layer: URLayer[EmailService with UserRepository, UserService] = - (UserServiceLive.apply _).toLayer[UserService] + ZLayer { + for { + emailService <- ZIO.service[EmailService] + userRepository <- ZIO.service[UserRepository] + } yield UserServiceLive(emailService, userRepository) + } } ``` From 8c527c5e0fe32afeb9939f92efa00a9e08e2c2fd Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sun, 4 Dec 2022 13:59:36 +0100 Subject: [PATCH 04/12] add zio-mock to docs dependencies --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 4a5031c8ca29..10cef583d1bb 100644 --- a/build.sbt +++ b/build.sbt @@ -877,6 +877,7 @@ lazy val docs = project.module "dev.zio" %% "zio-interop-twitter" % "20.10.0.0", "dev.zio" %% "zio-zmx" % "0.0.9", "dev.zio" %% "zio-query" % "0.2.10", + "dev.zio" %% "zio-mock" % "1.0.0-RC9", "org.polynote" %% "uzhttp" % "0.2.8", "org.tpolecat" %% "doobie-core" % doobieV, "org.tpolecat" %% "doobie-h2" % doobieV, From 078e26ca41e3956f7888aa9db2c275f81af2bb13 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sun, 4 Dec 2022 14:05:21 +0100 Subject: [PATCH 05/12] testing services mdoc'ed and compiling --- docs/ecosystem/officials/zio-mock.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index 436eca8b8047..80ff95f07b02 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -120,8 +120,9 @@ For example, to encode the `send` capability of `EmailService` we need to extend Let's see how we can mock the `EmailService`: -```scala +```scala mdoc:silent // Test Sources +import zio._ import zio.mock._ object MockEmailService extends Mock[EmailService] { @@ -143,10 +144,7 @@ object MockEmailService extends Mock[EmailService] { And, here is the mock version of the `UserRepository`: -```scala -import zio._ -import zio.mock._ - +```scala mdoc:silent:nest object MockUserRepository extends Mock[UserRepository] { object Save extends Effect[User, String, Unit] @@ -170,7 +168,7 @@ After writing the mock version of collaborators, now we can use their _capabilit For example, we can create an expectation from the `Send` capability tag of the `MockEmailService`: -```scala +```scala mdoc:silent:nest import zio.test._ val sendEmailExpectation: Expectation[EmailService] = @@ -184,7 +182,7 @@ The `sendEmailExpectation` is an expectation, which requires a call to `send` me There is an extension method called `Expectation#toLayer` which implicitly converts an expectation to the `ZLayer` environment: -```scala +```scala mdoc:silent:nest import zio.test._ val mockEmailService: ULayer[EmailService] = @@ -200,8 +198,7 @@ So we do not require to convert them to `ZLayer` explicitly. It will convert the > If we register a user with an age of less than 18, we expect that the `save` method of `UserRepository` shouldn't be called. Additionally, we expect that the `send` method of `EmailService` will be called with the following content: "You are not eligible to register." -```scala -import zio.test._ +```scala mdoc:silent:nest test("non-adult registration") { val sut = UserService.register("john", 15, "john@doe") @@ -224,8 +221,7 @@ We used `MockUserRepository.empty` since we expect no call to the `UserRepositor > If we register a user with a username of "admin", we expect that both `UserRepository` and `EmailService` should not be called. Instead, we expect that the `register` call will be failed with a proper failure value: "The admin user is already registered!" -```scala -import zio.test._ +```scala mdoc:silent:nest test("user cannot register pre-defined admin user") { val sut = UserService.register("admin", 30, "admin@doe") @@ -253,8 +249,7 @@ test("user cannot register pre-defined admin user") { > We expect that the `save` method of `UserRepository` will be called with the corresponding `User` object, and the `send` method of `EmailService` will be called with this content: "Congratulation, you are registered!". -```scala -import zio.test._ +```scala mdoc:silent:nest test("a valid user can register to the user service") { val sut = UserService.register("jane", 25, "jane@doe") From bd6cf067b26ce8791622f1ad0792760ca20bb634 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sun, 4 Dec 2022 15:48:04 +0100 Subject: [PATCH 06/12] `Encoding Polymorphic Capabilities` mdoc'ed and compiling --- docs/ecosystem/officials/zio-mock.md | 42 +++++++++++++--------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index 80ff95f07b02..579278b466f7 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -287,7 +287,7 @@ In order to create a mock object, we should define an object which implements th Capabilities are service functionalities that are accessible from the client-side. For example, in the following service the `send` method is a service capability: -```scala +```scala mdoc:silent import zio._ trait UserService { @@ -311,7 +311,7 @@ We can have 4 types of capabilities inside a service: Let's say we have the following service: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.stream._ @@ -326,7 +326,7 @@ trait ExampleService { Therefore, the mock service should have the following _capability tags_: -```scala +```scala mdoc:silent:nest import zio.mock._ object MockExampleService extends Mock[ExampleService] { @@ -353,7 +353,7 @@ We encode service capabilities according to the following scheme: For zero arguments the type is `Unit` -```scala +```scala mdoc:silent import zio._ trait ZeroParamService { @@ -363,7 +363,7 @@ trait ZeroParamService { So the capability tag of `zeroParams` should be: -```scala +```scala mdoc:silent:nest import zio.mock._ object MockZeroParamService extends Mock[ZeroParamService] { @@ -387,7 +387,7 @@ For one or more arguments, regardless of how many parameter lists, the type is a If the capability has more than one argument, we should encode the argument types in the `Tuple` data type. For example, if we have the following service: -```scala +```scala mdoc:silent import zio._ trait ManyParamsService { @@ -398,7 +398,7 @@ trait ManyParamsService { We should encode that with the following capability tag: -```scala +```scala mdoc:silent:nest import zio.mock._ trait MockExampleService extends Mock[ManyParamsService] { @@ -413,7 +413,7 @@ trait MockExampleService extends Mock[ManyParamsService] { For overloaded methods, we nest a list of numbered objects, each representing subsequent overloads: -```scala +```scala mdoc:silent // Main sources import zio._ @@ -427,10 +427,9 @@ trait OverloadedService { We encode both overloaded capabilities by using numbered objects inside a nested object: -```scala +```scala mdoc:silent:nest // Test sources -import zio._ import zio.mock._ object MockOervloadedService extends Mock[OverloadedService] { @@ -451,7 +450,7 @@ object MockOervloadedService extends Mock[OverloadedService] { Mocking polymorphic methods is also supported, but the interface must require `zio.Tag` implicit evidence for each type parameter: -```scala +```scala mdoc:silent // main sources import zio._ @@ -465,7 +464,7 @@ trait PolyService { In the test sources we construct partially applied _capability tags_ by extending `Method.Poly` family. The unknown types must be provided at call site. To produce a final monomorphic `Method` tag we must use the `of` combinator and pass the missing types: -```scala +```scala mdoc:silent:nest // test sources import zio.mock._ @@ -479,14 +478,12 @@ object MockPolyService extends Mock[PolyService] { // We will learn about the compose layer in the next section val compose: URLayer[Proxy, PolyService] = ZLayer.fromZIO( - ZIO.serviceWithZIO[Proxy] { proxy => - withRuntime[Any].map { rts => + ZIO.service[Proxy].map { proxy => new PolyService { def polyInput[I: Tag](input: I) = proxy(PolyInput.of[I], input) def polyError[E: Tag](input: Int) = proxy(PolyError.of[E], input) def polyOutput[A: Tag](input: Int) = proxy(PolyOutput.of[A], input) def polyAll[I: Tag, E: Tag, A: Tag](input: I) = proxy(PolyAll.of[I, E, A], input) - } } } ) @@ -495,31 +492,30 @@ object MockPolyService extends Mock[PolyService] { Similarly, we use the same `of` combinator to refer to concrete monomorphic call in our test suite when building expectations: -```scala +```scala mdoc:silent:nest import zio.test._ -import MockPolyService._ -val exp06 = PolyInput.of[String]( +val exp06 = MockPolyService.PolyInput.of[String]( Assertion.equalTo("foo"), Expectation.value("bar") ) -val exp07 = PolyInput.of[Int]( +val exp07 = MockPolyService.PolyInput.of[Int]( Assertion.equalTo(42), Expectation.failure(new Exception) ) -val exp08 = PolyInput.of[Long]( +val exp08 = MockPolyService.PolyInput.of[Long]( Assertion.equalTo(42L), Expectation.value("baz") ) -val exp09 = PolyAll.of[Int, Throwable, String]( +val exp09 = MockPolyService.PolyAll.of[Int, Throwable, String]( Assertion.equalTo(42), Expectation.value("foo") ) -val exp10 = PolyAll.of[Int, Throwable, String]( +val exp10 = MockPolyService.PolyAll.of[Int, Throwable, String]( Assertion.equalTo(42), Expectation.failure(new Exception) ) @@ -532,7 +528,7 @@ Finally, we need to define a _compose layer_ that can create our environment fro So again, assume we have the following service: -```scala +```scala import zio._ import zio.mock._ From ce1875bcf2d8ea3a06742dcabf7fd7947b41e6c6 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sun, 4 Dec 2022 19:34:38 +0100 Subject: [PATCH 07/12] all `zio-mock.md` properly mdoc'ed --- docs/ecosystem/officials/zio-mock.md | 106 +++++++++++++++------------ 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index 579278b466f7..de82894f0507 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -528,7 +528,7 @@ Finally, we need to define a _compose layer_ that can create our environment fro So again, assume we have the following service: -```scala +```scala mdoc:silent import zio._ import zio.mock._ @@ -542,9 +542,7 @@ trait ExampleService { In this step, we need to provide a layer in which used to construct the mocked object. To do that, we should obtain the `Proxy` data type from the environment and then implement the service interface by wrapping all capability tags with proxy: -```scala -import zio.mock._ - +```scala mdoc:silent:nest object MockExampleService extends Mock[ExampleService] { object ExampleEffect extends Effect[Int, Throwable, String] object ExampleMethod extends Method[Int, Throwable, String] @@ -554,20 +552,28 @@ object MockExampleService extends Mock[ExampleService] { override val compose: URLayer[Proxy, ExampleService] = ZLayer.fromZIO( ZIO.serviceWithZIO[Proxy] { proxy => - withRuntime[Any].map { rts => - new ExampleService { - override def exampleEffect(i: Int): Task[String] = - proxy(ExampleEffect, i) - - override def exampleMethod(i: Int): String = - rts.unsafeRunTask(proxy(ExampleMethod, i)) - - override def exampleSink(a: Int): stream.Sink[Throwable, Int, Nothing, List[Int]] = - rts.unsafeRun(proxy(ExampleSink, a)) - - override def exampleStream(a: Int): stream.Stream[Throwable, String] = - rts.unsafeRun(proxy(ExampleStream, a)) - } + withRuntime[Proxy, ExampleService] { rts => + ZIO.succeed( + new ExampleService { + override def exampleEffect(i: Int): Task[String] = + proxy(ExampleEffect, i) + + override def exampleMethod(i: Int): String = + Unsafe.unsafe { implicit u => + rts.unsafe.run(proxy(ExampleMethod, i)).getOrThrow() + } + + override def exampleSink(a: Int): stream.Sink[Throwable, Int, Nothing, List[Int]] = + Unsafe.unsafe { implicit u => + rts.unsafe.run(proxy(ExampleSink, a)).getOrThrow() + } + + override def exampleStream(a: Int): stream.Stream[Throwable, String] = + Unsafe.unsafe { implicit u => + rts.unsafe.run(proxy(ExampleStream, a)).getOrThrow() + } + } + ) } } ) @@ -584,11 +590,11 @@ A reference to this layer is passed to _capability tags_, so it can be used to a ## The Complete Example -```scala +```scala mdoc:silent trait AccountEvent ``` -```scala +```scala mdoc:silent:nest // main sources import zio._ @@ -620,11 +626,16 @@ case class AccountObserverLive(console: Console) extends AccountObserver { } object AccountObserverLive { - val layer = (AccountObserverLive.apply _).toLayer[AccountObserver] + val layer = + ZLayer { + for { + console <- ZIO.service[Console] + } yield AccountObserverLive(console) + } } ``` -```scala +```scala mdoc:silent:nest // test sources object AccountObserverMock extends Mock[AccountObserver] { @@ -669,7 +680,7 @@ An `Expectation[R]` is an immutable tree structure that represents expectations ZIO Test has a variety of expectations, such as `value`, `unit`, `failure`, and `never`. In this section we are going to learn each of these expectations and their variant, by mocking the `UserService` service. So let's assume we have the following service: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test._ @@ -704,7 +715,7 @@ object UserService { We can write the mock version of this class as below: -```scala +```scala mdoc:silent:nest object MockUserService extends Mock[UserService] { @@ -735,7 +746,7 @@ To create expectations we use the previously defined _capability tags_. 1. For methods that take input, the first argument will be an assertion on input, and the second the predefined result. -```scala +```scala mdoc:silent:nest import zio.mock._ import zio.test._ @@ -747,13 +758,13 @@ val exp01 = MockUserService.RecentUsers( // capability to build an expectation f 2. For methods that take no input, we only define the expected output: -```scala +```scala mdoc:silent:nest val exp02 = MockUserService.TotalUsers(Expectation.value(42)) ``` 3. For methods that may return `Unit`, we may skip the predefined result (it will default to successful value) or use `unit` helper: -```scala +```scala mdoc:silent:nest val exp03 = MockUserService.Remove( Assertion.equalTo("1"), Expectation.unit @@ -762,7 +773,7 @@ val exp03 = MockUserService.Remove( 4. For methods that may return `Unit` and take no input we can skip both: -```scala +```scala mdoc:silent:nest val exp04 = MockUserService.RemoveAll() ``` @@ -770,7 +781,7 @@ val exp04 = MockUserService.RemoveAll() Each expectation can be taught of a mocked environment. They can be converted to a `ZLayer` implicitly. Therefore, we can compose them together and provide them to the environment of the SUT (System Under Test). -```scala +```scala mdoc:silent:nest import zio.test._ import zio._ @@ -790,7 +801,7 @@ test("expecting simple value on call to nextInt") { Often the dependency on a collaborator is only in some branches of the code. To test the correct behaviour of branches without dependencies, we still have to provide it to the environment, but we would like to assert it was never called. With the `Mock.empty` method we can obtain a `ZLayer` with an empty service (no calls expected): -```scala +```scala mdoc:silent import zio.mock._ import zio.test._ @@ -822,7 +833,7 @@ object MaybeConsoleSpec extends MockSpecDefault { In some cases we have more than one collaborating service being called. We can create mocks for rich environments and as you enrich the environment by using _capability tags_ from another service, the underlying mocked layer will be updated. -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -860,7 +871,7 @@ In the most robust example, the result can be either a successful value or a fai Expecting a simple value: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -878,7 +889,7 @@ test("expecting simple value") { Expecting a value based on input arguments: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -902,7 +913,7 @@ test("an expectation based on input arguments") { Expecting a value based on the input arguments and also the result of an effectful operation: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -916,7 +927,6 @@ test("effectful expectation") { Random .nextUUID .map(id => User(id.toString, s"name-$n")) - .provideLayer(Random.live) }.map(_.toList) ) ) @@ -931,7 +941,7 @@ test("effectful expectation") { Expecting simple unit value: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -958,7 +968,7 @@ test("expecting unit") { Expecting a failure: -```scala +```scala mdoc/silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -986,7 +996,7 @@ There are also `failureF` and `failureZIO` variants like what we described for ` This expectation simulates a never-ending loop: -```scala +```scala mdoc/silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1013,7 +1023,7 @@ We can combine our expectation to build complex scenarios using combinators defi The `and` (alias `&&`) operator composes two expectations, producing a new expectation to **satisfy both in any order**: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1036,7 +1046,7 @@ test("satisfy both expectations with a logical `and` operator") { The `or` (alias `||`) operator composes two expectations, producing a new expectation to **satisfy only one of them**: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1059,7 +1069,7 @@ test("satisfy one of expectations with a logical `or` operator") { The `andThen` (alias `++`) operator composes two expectations, producing a new expectation to **satisfy both sequentially**: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1083,7 +1093,7 @@ In the example above, changing the SUT to `UserService.totalUsers *> UserService 1. **`exactly`** — Produces a new expectation to satisfy itself exactly the given number of times: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1106,7 +1116,7 @@ test("satisfying exact repetition of a method call") { 1. **`Expectation#repeats(range: Range)`** — Repeats this expectation within given bounds, producing a new expectation to **satisfy itself sequentially given number of times**: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1125,7 +1135,7 @@ In the example above, if we repeat `nextInt` less than 2 or over 4 times, the te Another note on repetitions is that, if we compose expectations with `andThen`/`++`, once another repetition starts executing, it must be completed in order to satisfy the composite expectation. For example `(A ++ B).repeats(1, 2)` will be satisfied by either `A->B` (one repetition) or `A->B->A->B` (two repetitions), but will fail on `A->B->A` (incomplete second repetition): -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.mock.Expectation._ @@ -1150,7 +1160,7 @@ test("if another repetition starts executing, it must be completed") { Here is an example of mocking `Clock.nanoTime` capability: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.mock.Expectation._ @@ -1168,7 +1178,7 @@ test("calling mocked nanoTime should return expected time") { Here is an example of mocking `Console.readLine` capability: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1186,7 +1196,7 @@ test("calling mocked readline should return expected value") { Here's how we can mock the `MockRandom.nextIntBounded` capability: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -1207,7 +1217,7 @@ test("expect call with input satisfying assertion and transforming it into outpu Here's how we can mock the `MockSystem.property` capability: -```scala +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} From 1528c7da1d42ae9c95e6a776646c28d974d9113a Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Sun, 4 Dec 2022 23:26:54 +0100 Subject: [PATCH 08/12] fixed typos --- docs/ecosystem/officials/zio-mock.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index de82894f0507..bc51ec24c17c 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -968,7 +968,7 @@ test("expecting unit") { Expecting a failure: -```scala mdoc/silent +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} @@ -996,7 +996,7 @@ There are also `failureF` and `failureZIO` variants like what we described for ` This expectation simulates a never-ending loop: -```scala mdoc/silent +```scala mdoc:silent import zio._ import zio.mock._ import zio.test.{test, _} From ef7be69d9c44714eac23329f3d575c968aa3be45 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Mon, 5 Dec 2022 00:04:16 +0100 Subject: [PATCH 09/12] more idiomatic ZLayer constructors --- docs/ecosystem/officials/zio-mock.md | 49 +++++++++++++--------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index bc51ec24c17c..62d0fa52050f 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -67,12 +67,7 @@ case class UserServiceLive(emailService: EmailService, userRepository: UserRepos object UserServiceLive { val layer: URLayer[EmailService with UserRepository, UserService] = - ZLayer { - for { - emailService <- ZIO.service[EmailService] - userRepository <- ZIO.service[UserRepository] - } yield UserServiceLive(emailService, userRepository) - } + ZLayer.fromFunction(UserServiceLive.apply _) } ``` @@ -129,16 +124,16 @@ object MockEmailService extends Mock[EmailService] { object Send extends Effect[(String, String), String, Unit] val compose: URLayer[Proxy, EmailService] = - ZLayer.fromZIO( - ZIO - .service[Proxy] - .map { proxy => - new EmailService { - override def send(to: String, body: String): IO[String, Unit] = - proxy(Send, to, body) - } + ZLayer { + for { + proxy <- ZIO.service[Proxy] + } yield ( + new EmailService { + override def send(to: String, body: String): IO[String, Unit] = + proxy(Send, to, body) } - ) + ) + } } ``` @@ -149,16 +144,16 @@ object MockUserRepository extends Mock[UserRepository] { object Save extends Effect[User, String, Unit] val compose: URLayer[Proxy, UserRepository] = - ZLayer.fromZIO( - ZIO - .service[Proxy] - .map { proxy => - new UserRepository { - override def save(user: User): IO[String, Unit] = - proxy(Save, user) - } + ZLayer { + for { + proxy <- ZIO.service[Proxy] + } yield ( + new UserRepository { + override def save(user: User): IO[String, Unit] = + proxy(Save, user) } - ) + ) + } } ``` @@ -477,8 +472,10 @@ object MockPolyService extends Mock[PolyService] { // We will learn about the compose layer in the next section val compose: URLayer[Proxy, PolyService] = - ZLayer.fromZIO( - ZIO.service[Proxy].map { proxy => + ZLayer( + for { + proxy <- ZIO.service[Proxy] + } yield { new PolyService { def polyInput[I: Tag](input: I) = proxy(PolyInput.of[I], input) def polyError[E: Tag](input: Int) = proxy(PolyError.of[E], input) From 15a5b6c039ad2658171c36f0d66da9f10114de1b Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Mon, 5 Dec 2022 00:10:01 +0100 Subject: [PATCH 10/12] more idiomatic ZLayer constructor --- docs/ecosystem/officials/zio-mock.md | 29 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index 62d0fa52050f..646ad355b9cb 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -641,14 +641,15 @@ object AccountObserverMock extends Mock[AccountObserver] { object RunCommand extends Effect[Unit, Nothing, Unit] val compose: URLayer[Proxy, AccountObserver] = - ZLayer.fromZIO( - ZIO.service[Proxy].map { proxy => - new AccountObserver { - def processEvent(event: AccountEvent) = proxy(ProcessEvent, event) - def runCommand(): UIO[Unit] = proxy(RunCommand) - } - } - ) + ZLayer { + for { + proxy <- ZIO.service[Proxy] + } yield + new AccountObserver { + def processEvent(event: AccountEvent) = proxy(ProcessEvent, event) + def runCommand(): UIO[Unit] = proxy(RunCommand) + } + } } ``` @@ -723,9 +724,10 @@ object MockUserService extends Mock[UserService] { object RemoveAll extends Effect[Unit, String, Unit] val compose: URLayer[mock.Proxy, UserService] = - ZLayer.fromZIO( - ZIO.service[mock.Proxy] - .map { proxy => + ZLayer{ + for { + proxy <- ZIO.service[mock.Proxy] + } yield new UserService { override def insert(user: User): IO[String, Unit] = proxy(Insert, user) override def remove(id: String): IO[String, Unit] = proxy(Remove, id) @@ -733,9 +735,8 @@ object MockUserService extends Mock[UserService] { override def totalUsers: IO[String, Int] = proxy(TotalUsers) override def removeAll: IO[String, Unit] = proxy(RemoveAll) } - } - ) - + + } } ``` From 98ef4f361d4c73189b71e2511e39947bfd8b6b24 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Tue, 6 Dec 2022 12:09:16 +0100 Subject: [PATCH 11/12] idiomatic yielding and use of curly braces. Imported MockPolyService --- docs/ecosystem/officials/zio-mock.md | 89 +++++++++++++--------------- 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index 646ad355b9cb..fbc4837ad9d6 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -127,12 +127,10 @@ object MockEmailService extends Mock[EmailService] { ZLayer { for { proxy <- ZIO.service[Proxy] - } yield ( - new EmailService { - override def send(to: String, body: String): IO[String, Unit] = - proxy(Send, to, body) - } - ) + } yield new EmailService { + override def send(to: String, body: String): IO[String, Unit] = + proxy(Send, to, body) + } } } ``` @@ -147,12 +145,10 @@ object MockUserRepository extends Mock[UserRepository] { ZLayer { for { proxy <- ZIO.service[Proxy] - } yield ( - new UserRepository { - override def save(user: User): IO[String, Unit] = - proxy(Save, user) - } - ) + } yield new UserRepository { + override def save(user: User): IO[String, Unit] = + proxy(Save, user) + } } } ``` @@ -472,18 +468,16 @@ object MockPolyService extends Mock[PolyService] { // We will learn about the compose layer in the next section val compose: URLayer[Proxy, PolyService] = - ZLayer( + ZLayer { for { proxy <- ZIO.service[Proxy] - } yield { - new PolyService { + } yield new PolyService { def polyInput[I: Tag](input: I) = proxy(PolyInput.of[I], input) def polyError[E: Tag](input: Int) = proxy(PolyError.of[E], input) def polyOutput[A: Tag](input: Int) = proxy(PolyOutput.of[A], input) def polyAll[I: Tag, E: Tag, A: Tag](input: I) = proxy(PolyAll.of[I, E, A], input) } - } - ) + } } ``` @@ -491,28 +485,29 @@ Similarly, we use the same `of` combinator to refer to concrete monomorphic call ```scala mdoc:silent:nest import zio.test._ +import MockPolyService._ -val exp06 = MockPolyService.PolyInput.of[String]( +val exp06 = PolyInput.of[String]( Assertion.equalTo("foo"), Expectation.value("bar") ) -val exp07 = MockPolyService.PolyInput.of[Int]( +val exp07 = PolyInput.of[Int]( Assertion.equalTo(42), Expectation.failure(new Exception) ) -val exp08 = MockPolyService.PolyInput.of[Long]( +val exp08 = PolyInput.of[Long]( Assertion.equalTo(42L), Expectation.value("baz") ) -val exp09 = MockPolyService.PolyAll.of[Int, Throwable, String]( +val exp09 = PolyAll.of[Int, Throwable, String]( Assertion.equalTo(42), Expectation.value("foo") ) -val exp10 = MockPolyService.PolyAll.of[Int, Throwable, String]( +val exp10 = PolyAll.of[Int, Throwable, String]( Assertion.equalTo(42), Expectation.failure(new Exception) ) @@ -547,33 +542,33 @@ object MockExampleService extends Mock[ExampleService] { object ExampleStream extends Stream[Int, Throwable, String] override val compose: URLayer[Proxy, ExampleService] = - ZLayer.fromZIO( + ZLayer { ZIO.serviceWithZIO[Proxy] { proxy => - withRuntime[Proxy, ExampleService] { rts => - ZIO.succeed( - new ExampleService { + withRuntime[Proxy, ExampleService] { runtime => + ZIO.succeed { + ExampleService { override def exampleEffect(i: Int): Task[String] = proxy(ExampleEffect, i) override def exampleMethod(i: Int): String = - Unsafe.unsafe { implicit u => - rts.unsafe.run(proxy(ExampleMethod, i)).getOrThrow() + Unsafe.unsafe { implicit unsafe => + runtime.unsafe.run(proxy(ExampleMethod, i)).getOrThrow() } override def exampleSink(a: Int): stream.Sink[Throwable, Int, Nothing, List[Int]] = - Unsafe.unsafe { implicit u => - rts.unsafe.run(proxy(ExampleSink, a)).getOrThrow() + Unsafe.unsafe { implicit unsafe => + runtime.unsafe.run(proxy(ExampleSink, a)).getOrThrow() } override def exampleStream(a: Int): stream.Stream[Throwable, String] = - Unsafe.unsafe { implicit u => - rts.unsafe.run(proxy(ExampleStream, a)).getOrThrow() + Unsafe.unsafe { implicit unsafe => + runtime.unsafe.run(proxy(ExampleStream, a)).getOrThrow() } } - ) + } } } - ) + } } ``` @@ -644,11 +639,10 @@ object AccountObserverMock extends Mock[AccountObserver] { ZLayer { for { proxy <- ZIO.service[Proxy] - } yield - new AccountObserver { - def processEvent(event: AccountEvent) = proxy(ProcessEvent, event) - def runCommand(): UIO[Unit] = proxy(RunCommand) - } + } yield new AccountObserver { + def processEvent(event: AccountEvent) = proxy(ProcessEvent, event) + def runCommand(): UIO[Unit] = proxy(RunCommand) + } } } ``` @@ -724,17 +718,16 @@ object MockUserService extends Mock[UserService] { object RemoveAll extends Effect[Unit, String, Unit] val compose: URLayer[mock.Proxy, UserService] = - ZLayer{ + ZLayer { for { proxy <- ZIO.service[mock.Proxy] - } yield - new UserService { - override def insert(user: User): IO[String, Unit] = proxy(Insert, user) - override def remove(id: String): IO[String, Unit] = proxy(Remove, id) - override def recentUsers(n: Int): IO[String, List[User]] = proxy(RecentUsers, n) - override def totalUsers: IO[String, Int] = proxy(TotalUsers) - override def removeAll: IO[String, Unit] = proxy(RemoveAll) - } + } yield new UserService { + override def insert(user: User): IO[String, Unit] = proxy(Insert, user) + override def remove(id: String): IO[String, Unit] = proxy(Remove, id) + override def recentUsers(n: Int): IO[String, List[User]] = proxy(RecentUsers, n) + override def totalUsers: IO[String, Int] = proxy(TotalUsers) + override def removeAll: IO[String, Unit] = proxy(RemoveAll) + } } } From 0e9f9b0902218cf2f3edb9994ae51b782a67a948 Mon Sep 17 00:00:00 2001 From: Joaquin Iglesias Turina Date: Tue, 6 Dec 2022 12:31:03 +0100 Subject: [PATCH 12/12] added missing new constructor. --- docs/ecosystem/officials/zio-mock.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ecosystem/officials/zio-mock.md b/docs/ecosystem/officials/zio-mock.md index fbc4837ad9d6..bb93bb0e44a2 100644 --- a/docs/ecosystem/officials/zio-mock.md +++ b/docs/ecosystem/officials/zio-mock.md @@ -546,7 +546,7 @@ object MockExampleService extends Mock[ExampleService] { ZIO.serviceWithZIO[Proxy] { proxy => withRuntime[Proxy, ExampleService] { runtime => ZIO.succeed { - ExampleService { + new ExampleService { override def exampleEffect(i: Int): Task[String] = proxy(ExampleEffect, i)