Skip to content

Commit

Permalink
Update README for test lib
Browse files Browse the repository at this point in the history
  • Loading branch information
alterationx10 committed Jul 7, 2023
1 parent 8bdf836 commit 3f7f0b2
Showing 1 changed file with 149 additions and 1 deletion.
150 changes: 149 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ val configLayer: Layer[ReadError[String], Config] =
// Automatically derive a generic ZLayer
trait Service

case class ServiceImpl(p: Int, cfg: Config) extends Service derives AutoLayer
// My markdown formatter keeps moving 'derives AutoLayer', so I added it
// behind a comment line here :-(
case class ServiceImpl(p: Int, cfg: Config) extends Service //derives AutoLayer

val implLayer: ZLayer[Int & Config, Nothing, ServiceImpl] =
AutoLayer[ServiceImpl]
Expand All @@ -106,3 +108,149 @@ import test.opinons.*
```

More docs to come, but please check the corresponding tests for examples.

### Assertions

```scala

// Wrap a value in Assertion.equalTo
val eq1: Assertion[Int] = 1.eqTo

// Negates wrapping a value in Assertion.EqualTo
val neq1: Assertion[Int] = 1.neqTo

```

### Expectations

```scala

// Wrap a value in Expectation.value
val expect1: Result[Any, Nothing, Int] = 1.expected

// Wrap a value in Expectation.failure
val fail: Result[Any, Throwable, Nothing] = new Exception("boom").expectedF


```

### Gens

ZIO has Gens for generative testing, but I also like to use them to make
instances of models in other tests (i.e. integration tests for DB, etc...).

```scala

case class Thing(a: String)

// Am implicit Gen[Any, Thing] will need to be in scope.
// You can use something like this to derive a Gen if needed
// given Gen[Any, Thing] = DeriveGen[Thing]

// Generate a List of 42 random Things
val thingList: UIO[List[Thing]] = GenRandom[Thing](42)

// Generate a random Thing
val thing: UIO[Thing] = GenRandom[Thing]

```

### Mocks

These methods are mostly to add different ergonomics around Expectations as
layers. It adds things `expectWhen` (or `whenExpect`), `expectWhenF` (for
failures), and `.when(???).expect(???)` (partially applying the arguments).

Considering the following Service + Mock

```
trait SomeService:
def get(id: Int): Task[String]
def multi(a: String, b: Int): Task[String]
object SomeMockService extends Mock[SomeService]:
object Get extends Effect[Int, Throwable, String]
object Multi extends Effect[(String, Int), Throwable, String]
val compose: URLayer[Proxy, SomeService] =
ZLayer {
for {
proxy <- ZIO.service[Proxy]
} yield new SomeService:
override def get(id: RuntimeFlags): Task[String] = proxy(Get, id)
override def multi(a: String, b: RuntimeFlags): Task[String] =
proxy(Multi, a, b)
}
```

We could do some the following:

```scala
test("expect/when of values") {
for {
result <- ZIO
.serviceWithZIO[SomeService](_.get(42))
.provide(SomeMockService.Get.expectWhen("forty two", 42))
// partial application extensions
_ <- ZIO
.serviceWithZIO[SomeService](_.get(42))
.provide(
SomeMockService.Get
.when(42)
.expect("c")
)
// Comparison without extension method
_ <- ZIO
.serviceWithZIO[SomeService](_.get(42))
.provide(
SomeMockService
.Get(Assertion.equalTo(42), Expectation.value("forty two"))
)
// Mocking a failure
error <-
ZIO
.serviceWithZIO[SomeService](_.get(42))
.flip
.provide(
SomeMockService.Get.expectWhenF(new Exception("boom"), 42)
)
} yield assertTrue(
result == "forty two",
error.getMessage == "boom"
)
}
```

One pain with mocking services that multi-argument are Tuples, but expects
a (single) Assertion of that Tuple type.

It would be wonderful to provide assertions on the tuple elements, instead of
one blanket assertion of a properly typed tuple. For example, maybe I only care
about the value of the first element of a Tuple3, and the others can be
anything.

This provides (up to Tuple5 so far, because I'm lazy) extension methods to
take a Tuple of Assertions to make it an Assertion of the appropriately
typed Tuple, checking the values along the way.

These are generally implemented as:

```scala
extension[A, B, C](t: (Assertion[A], Assertion[B], Assertion[C]))
/** Convert a Tuple of Assertions into an Assertion of a Tuple
*/
def asserted: Assertion[(A, B, C)] =
Assertion.hasField("_1", (tt: (A, B, C)) => tt._1, t._1) &&
Assertion.hasField("_2", (tt: (A, B, C)) => tt._2, t._2) &&
Assertion.hasField("_3", (tt: (A, B, C)) => tt._3, t._3)
```

With these, we can write a Mock expectation like below, where we only care
about a specific value for the first method argument, but the second can be
anything.

```scala
val justA = SomeMockService.Multi
.when(("a".eqTo, Assertion.anything).asserted)
.expect("success")
```

0 comments on commit 3f7f0b2

Please sign in to comment.