-
Notifications
You must be signed in to change notification settings - Fork 419
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Documentation of Testing HTTP Applications (#2774)
* testing http applications. * zio test section.
- Loading branch information
Showing
4 changed files
with
139 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
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 |
---|---|---|
|
@@ -43,6 +43,7 @@ const sidebars = { | |
"dsl/client" | ||
] | ||
}, | ||
"testing-http-apps", | ||
{ | ||
type: "category", | ||
label: "Examples", | ||
|
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,135 @@ | ||
--- | ||
id: testing-http-apps | ||
title: Testing HTTP Applications | ||
--- | ||
|
||
Testing HTTP applications is a critical part of the development process. Utilizing the ZIO Test we can write first-class tests for our HTTP applications. | ||
|
||
## ZIO Test | ||
|
||
We have comprehensive documentation on [ZIO Test](https://zio.dev/reference/test/) which is worth reading to understand how to write tests using ZIO effects. | ||
|
||
It is easy to test ZIO HTTP applications beacuse we can think of `HttpApp` as a function of `Request => ZIO[R, Response, Response]`. This means we can effortlessly provide a Request as input to the `HttpApp` and receive the corresponding Response as output using the runZIO method. By doing this we can test the behavior of the `HttpApp` in a controlled environment: | ||
|
||
```scala mdoc:silent:reset | ||
import zio.test._ | ||
import zio.test.Assertion.equalTo | ||
import zio.http._ | ||
|
||
object ExampleSpec extends ZIOSpecDefault { | ||
|
||
def spec = suite("http")( | ||
test("should be ok") { | ||
val app = Handler.ok.toHttpApp | ||
val req = Request.get(URL(Root)) | ||
assertZIO(app.runZIO(req))(equalTo(Response.ok)) | ||
} | ||
) | ||
} | ||
``` | ||
|
||
## ZIO HTTP Testkit | ||
|
||
Also, ZIO HTTP provides a testkit called `zio-http-testkit` that includes `TestClient` and `TestServer` utilities which helps us to test our HTTP applications without the need for having a real live client and server instances. | ||
|
||
```scala | ||
libraryDependencies += "dev.zio" %% "zio-test" % ZioVersion % Test | ||
libraryDependencies += "dev.zio" %% "zio-test-sbt" % ZioVersion % Test | ||
libraryDependencies += "dev.zio" %% "zio-http-testkit" % "@VERSION@" % Test | ||
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") | ||
``` | ||
|
||
Now, based on the requirement we can use any of the following test utilities: | ||
|
||
## TestClinet | ||
|
||
The `TestClient` | ||
|
||
Using the `TestClient` we can write tests for our HTTP applications without starting a live server instance. | ||
|
||
Using following methods we can define the behavior of the `TestClient`: | ||
|
||
- `TestClient.addRequestResponse` - Adds an exact 1-1 behavior. It takes a request and a response and returns a `ZIO[TestClient, Nothing, Unit]`. | ||
- `TestClient.addRoute` and `addRouts` - Adds a route definition to handle requests that are submitted by test cases. It takes a `Route` or `Routes` and returns a `ZIO[R with TestClient, Nothing, Unit]`. | ||
- `TestClient.installSocketApp` - Installs a `WebSocketApp` to the `TestClient`. | ||
|
||
After defining the behavior of the test client, we can use the `TestClient.layer` to provide the `TestClient` and `Client` to the test cases: | ||
|
||
```scala mdoc:compile-only | ||
import zio._ | ||
import zio.http._ | ||
import zio.test.{test, _} | ||
|
||
object TestUsingTestClient extends ZIOSpecDefault { | ||
def spec = | ||
test("hello world route and fallback") { | ||
for { | ||
client <- ZIO.service[Client] | ||
_ <- TestClient.addRoutes { | ||
Routes( | ||
Method.GET / trailing -> handler { Response.text("fallback") }, | ||
Method.GET / "hello" / "world" -> handler { Response.text("Hey there!") }, | ||
) | ||
} | ||
helloResponse <- client(Request.get(URL.root / "hello" / "world")) | ||
helloBody <- helloResponse.body.asString | ||
fallbackResponse <- client(Request.get(URL.root / "any")) | ||
fallbackBody <- fallbackResponse.body.asString | ||
} yield assertTrue(helloBody == "Hey there!", fallbackBody == "fallback") | ||
}.provide(TestClient.layer, Scope.default) | ||
} | ||
``` | ||
|
||
## TestServer | ||
|
||
Using the `TestServer` we can write tests for our HTTP applications by starting a live server instance on the localhost. | ||
|
||
Using the following methods we can define the behavior of the `TestServer`: | ||
|
||
- `TestServer.addRequestResponse` - Adds an exact 1-1 behavior. It takes a request and a response and returns a `ZIO[TestServer, Nothing, Unit]`. | ||
- `TestServer.addRoute` and `TestServer.addRouts` - Adds a route definition to handle requests that are submitted by test cases. It takes a `Route` or `Routes` and returns a `ZIO[R with TestServer, Nothing, Unit]`. | ||
- `TestServer.install` - Installs a `HttpApp` to the `TestServer`. | ||
|
||
After defining the behavior of the test server, we can use the `TestServer.layer` to provide the `TestServer` to any test cases that require `Server`: | ||
|
||
```scala mdoc:compile-only | ||
import zio._ | ||
import zio.http._ | ||
import zio.http.netty.NettyConfig | ||
import zio.http.netty.server.NettyDriver | ||
import zio.test._ | ||
|
||
object TestServerExampleSpec extends ZIOSpecDefault { | ||
|
||
def spec = suite("test http app") { | ||
test("test hello and fallback routes") { | ||
for { | ||
client <- ZIO.service[Client] | ||
port <- ZIO.serviceWith[Server](_.port) | ||
testRequest = Request | ||
.get(url = URL.root.port(port)) | ||
.addHeaders(Headers(Header.Accept(MediaType.text.`plain`))) | ||
_ <- TestServer.addRoutes { | ||
Routes( | ||
Method.GET / trailing -> handler { | ||
Response.text("fallback") | ||
}, | ||
Method.GET / "hello" / "world" -> handler { | ||
Response.text("Hey there!") | ||
}, | ||
) | ||
} | ||
helloResponse <- client(Request.get(testRequest.url / "hello" / "world")) | ||
helloBody <- helloResponse.body.asString | ||
fallbackResponse <- client(Request.get(testRequest.url / "any")) | ||
fallbackBody <- fallbackResponse.body.asString | ||
} yield assertTrue(helloBody == "Hey there!", fallbackBody == "fallback") | ||
}.provideSome[Client with Driver](TestServer.layer, Scope.default) | ||
}.provide( | ||
ZLayer.succeed(Server.Config.default.onAnyOpenPort), | ||
Client.default, | ||
NettyDriver.customized, | ||
ZLayer.succeed(NettyConfig.defaultWithFastShutdown), | ||
) | ||
} | ||
``` |
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