-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
433b079
commit ba621ce
Showing
10 changed files
with
745 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ jobs: | |
- uses: olafurpg/setup-scala@v11 | ||
with: | ||
java-version: [email protected] | ||
- run: sbt ci-release | ||
- run: sbt harness-modules/ci-release | ||
env: | ||
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} | ||
PGP_SECRET: ${{ secrets.PGP_SECRET }} | ||
|
This file contains 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
160 changes: 160 additions & 0 deletions
160
modules/harness-zio-test/shared/src/main/scala/harness/zio/test/mock/Mock.scala
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package harness.zio.test.mock | ||
|
||
import java.util.UUID | ||
import zio.* | ||
|
||
abstract class Mock[Z](implicit rTag: Tag[Z]) { myMock => | ||
|
||
private[mock] final val mockId: UUID = UUID.randomUUID | ||
|
||
override final def hashCode: Int = mockId.hashCode | ||
|
||
override final def equals(that: Any): Boolean = | ||
that.asInstanceOf[Matchable] match { | ||
case that: Mock[?] => this.mockId == that.mockId | ||
case _ => false | ||
} | ||
|
||
final def name: String = | ||
myMock.getClass.getSimpleName.stripSuffix("$") | ||
|
||
abstract class Capability[I, R, E, A] { myCapability => | ||
|
||
private[mock] final val capabilityId: UUID = UUID.randomUUID | ||
|
||
override final def hashCode: Int = capabilityId.hashCode | ||
|
||
override final def equals(that: Any): Boolean = | ||
that.asInstanceOf[Matchable] match { | ||
case that: Capability[?, ?, ?, ?] => this.capabilityId == that.capabilityId | ||
case _ => false | ||
} | ||
|
||
final def name: String = | ||
s"${myMock.name}<${myCapability.getClass.getSimpleName.stripSuffix("$")}>" | ||
|
||
// =====| |===== | ||
|
||
private[mock] def getMock: Mock[Z] = myMock | ||
|
||
// =====| |===== | ||
|
||
/** | ||
* Use these when creating the mocked layer. | ||
* It allows you to specify how the capability should be implemented. | ||
* These differ from seeded expectations, | ||
* in that implementations don't care what order they are called in, | ||
* or if they are called at all. | ||
*/ | ||
object implement { | ||
|
||
def zioI(f: I => ZIO[R, E, A]): Mocked[Z] = | ||
Mocked.single(myCapability, f) | ||
|
||
def zio(f: => ZIO[R, E, A]): Mocked[Z] = | ||
zioI(_ => f) | ||
|
||
def successI(f: I => A): Mocked[Z] = | ||
zioI(i => ZIO.succeed(f(i))) | ||
|
||
def success(f: => A): Mocked[Z] = | ||
zioI(_ => ZIO.succeed(f)) | ||
|
||
def failureI(f: I => E): Mocked[Z] = | ||
zioI(i => ZIO.fail(f(i))) | ||
|
||
def failure(f: => E): Mocked[Z] = | ||
zioI(_ => ZIO.fail(f)) | ||
|
||
} | ||
|
||
/** | ||
* Use these to seed expected calls to the capability. | ||
* It is very important to note that ordering is enforced. | ||
* If you seed: | ||
* - `MyMock.Capability1.seed.success(1)` | ||
* - `MyMock.Capability2.seed.success("test")` | ||
* and then call `capability2()` before calling `capability1()`, | ||
* ZIO will die with an error letting you know that a call to `capability2()` is expected next. | ||
*/ | ||
object seed { | ||
|
||
// append | ||
|
||
def zioI(f: I => ZIO[R, E, A]): URIO[Proxy & Z, Unit] = | ||
ZIO.serviceWithZIO[Proxy](_.appendSeed(myCapability, f)) | ||
|
||
def zio(f: => ZIO[R, E, A]): URIO[Proxy & Z, Unit] = | ||
zioI(_ => f) | ||
|
||
def successI(f: I => A): URIO[Proxy & Z, Unit] = | ||
zioI(i => ZIO.succeed(f(i))) | ||
|
||
def success(f: => A): URIO[Proxy & Z, Unit] = | ||
zioI(_ => ZIO.succeed(f)) | ||
|
||
def failureI(f: I => E): URIO[Proxy & Z, Unit] = | ||
zioI(i => ZIO.fail(f(i))) | ||
|
||
def failure(f: => E): URIO[Proxy & Z, Unit] = | ||
zioI(_ => ZIO.fail(f)) | ||
|
||
object prepend { | ||
|
||
def zioI(f: I => ZIO[R, E, A]): URIO[Proxy & Z, Unit] = | ||
ZIO.serviceWithZIO[Proxy](_.prependSeed(myCapability, f)) | ||
|
||
def zio(f: => ZIO[R, E, A]): URIO[Proxy & Z, Unit] = | ||
zioI(_ => f) | ||
|
||
def successI(f: I => A): URIO[Proxy & Z, Unit] = | ||
zioI(i => ZIO.succeed(f(i))) | ||
|
||
def success(f: => A): URIO[Proxy & Z, Unit] = | ||
zioI(_ => ZIO.succeed(f)) | ||
|
||
def failureI(f: I => E): URIO[Proxy & Z, Unit] = | ||
zioI(i => ZIO.fail(f(i))) | ||
|
||
def failure(f: => E): URIO[Proxy & Z, Unit] = | ||
zioI(_ => ZIO.fail(f)) | ||
|
||
} | ||
|
||
object append { | ||
|
||
def zioI(f: I => ZIO[R, E, A]): URIO[Proxy & Z, Unit] = | ||
ZIO.serviceWithZIO[Proxy](_.appendSeed(myCapability, f)) | ||
|
||
def zio(f: => ZIO[R, E, A]): URIO[Proxy & Z, Unit] = | ||
zioI(_ => f) | ||
|
||
def successI(f: I => A): URIO[Proxy & Z, Unit] = | ||
zioI(i => ZIO.succeed(f(i))) | ||
|
||
def success(f: => A): URIO[Proxy & Z, Unit] = | ||
zioI(_ => ZIO.succeed(f)) | ||
|
||
def failureI(f: I => E): URIO[Proxy & Z, Unit] = | ||
zioI(i => ZIO.fail(f(i))) | ||
|
||
def failure(f: => E): URIO[Proxy & Z, Unit] = | ||
zioI(_ => ZIO.fail(f)) | ||
|
||
} | ||
|
||
} | ||
|
||
} | ||
|
||
abstract class Effect[I, E, A] extends Capability[I, Any, E, A] | ||
|
||
// =====| |===== | ||
|
||
def empty: Mocked[Z] = Mocked.empty[Z](myMock) | ||
|
||
protected def buildInternal(proxy: Proxy): Z | ||
|
||
private[mock] final def build(proxy: Proxy): ZEnvironment[Z] = ZEnvironment(buildInternal(proxy)) | ||
|
||
} |
72 changes: 72 additions & 0 deletions
72
modules/harness-zio-test/shared/src/main/scala/harness/zio/test/mock/Mocked.scala
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package harness.zio.test.mock | ||
|
||
import cats.syntax.list.* | ||
import harness.zio.test.mock.Types.* | ||
import harness.zio.test.mock.error.MockError | ||
import zio.* | ||
|
||
sealed trait Mocked[Z] { self => | ||
|
||
final def ++[Z2](that: Mocked[Z2]): Mocked[Z & Z2] = | ||
(self.asInstanceOf[Mocked[Z & Z2]], that.asInstanceOf[Mocked[Z & Z2]]) match { | ||
case (self: Mocked.Building[Z & Z2], that: Mocked.Building[Z & Z2]) => | ||
(self.impls.keySet & that.impls.keySet).toList.toNel match { | ||
case None => | ||
new Mocked.Building[Z & Z2]( | ||
impls = self.impls ++ that.impls, | ||
mocks = self.mocks | that.mocks, | ||
) | ||
case Some(conflictingCapabilities) => | ||
Mocked.FailedDuringBuild(MockError.OverlappingCapabilityImplementations(conflictingCapabilities)) | ||
} | ||
case (self: Mocked.FailedDuringBuild[Z & Z2], _) => self | ||
case (_: Mocked.Building[Z & Z2], that: Mocked.FailedDuringBuild[Z & Z2]) => that | ||
} | ||
|
||
final def toLayer(implicit envTag: EnvironmentTag[Z]): ULayer[Proxy & Z] = | ||
self match { | ||
case self: Mocked.Building[Z] => | ||
Proxy.make(self.impls.asInstanceOf[Map[ErasedCapability, ErasedEffectImpl]]) >+> | ||
ZLayer.fromZIOEnvironment { | ||
ZIO.serviceWith[Proxy] { proxy => | ||
val zEnvs: List[ZEnvironment[?]] = | ||
self.mocks.toList.map { _.build(proxy) } | ||
val zEnv: ZEnvironment[Z] = | ||
zEnvs.foldLeft(ZEnvironment.empty) { _ ++ _ }.asInstanceOf[ZEnvironment[Z]] | ||
|
||
zEnv | ||
} | ||
} | ||
case Mocked.FailedDuringBuild(error) => | ||
ZLayer.die(error) | ||
} | ||
|
||
} | ||
object Mocked { | ||
|
||
// =====| Builders |===== | ||
|
||
private final class Building[Z]( | ||
private[Mocked] val impls: Map[ErasedCapabilityZ[? >: Z], ErasedEffectImpl], | ||
private[Mocked] val mocks: Set[Mock[? >: Z]], | ||
) extends Mocked[Z] | ||
|
||
private final case class FailedDuringBuild[Z]( | ||
error: Throwable, | ||
) extends Mocked[Z] | ||
|
||
// =====| |===== | ||
|
||
private[mock] def single[Z, I, R, E, A](capability: Mock[Z]#Capability[I, R, E, A], effect: I => ZIO[R, E, A]): Mocked[Z] = | ||
new Mocked.Building[Z]( | ||
Map(capability -> effect), | ||
Set(capability.getMock), | ||
) | ||
|
||
private[mock] def empty[Z](mock: Mock[Z]): Mocked[Z] = | ||
new Mocked.Building[Z]( | ||
Map.empty, | ||
Set(mock), | ||
) | ||
|
||
} |
Oops, something went wrong.