Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./coverage.lcov
path-to-lcov: ./coverage.lcov
128 changes: 108 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,141 @@
HTTP Server testing library inspired by
[node-supertest-fetch](https://github.com/jwalton/node-supertest-fetch).

## Install

```sh
deno add jsr:@deno-libs/superfetch
```

## Example

### Basic testing

Pass a `Deno.ServeHandler` to `makeFetch` and it returns a `fetch` function
bound to a throwaway server hosting that handler.

```ts
import { describe, it } from 'jsr:@std/testing/bdd'
import { makeFetch } from 'jsr:@deno-libs/superfetch'

describe('makeFetch', () => {
it('should work with HTTP handler', async () => {
const handler = (res) => new Response('Hello World')
it('should work with an HTTP handler', async () => {
const handler: Deno.ServeHandler = () => new Response('Hello World')

const fetch = makeFetch(s)
const fetch = makeFetch(handler)

const res = await fetch('/')

res.expect('Hello World')
})
})
```

### Assertions

`expectStatus`, `expectHeader` and `expect` are chainable. `expectBody` ends a
chain.

```ts
const handler: Deno.ServeHandler = () =>
new Response('teapot', {
status: 418,
headers: { 'Content-Type': 'text/plain' },
})

const fetch = makeFetch(handler)
const res = await fetch('/')

res
.expectStatus(418)
.expectHeader('Content-Type', /text/)
.expectHeader('Coffee-Allowed', null) // assert a header is absent
.expectBody('teapot')
```

`expect` dispatches on its arguments: a number checks the status, two arguments
check a header, anything else checks the body.

```ts
res.expect(418) // same as expectStatus(418)
res.expect('Content-Type', 'text/plain') // same as expectHeader(...)
res.expect('teapot') // same as expectBody('teapot')
```

The body is decoded from the response `Content-Type`: `application/json` is
parsed as JSON, `text/*` as text, anything else as an `ArrayBuffer`.

```ts
const handler: Deno.ServeHandler = () =>
new Response(JSON.stringify({ hello: 'world' }), {
headers: { 'Content-Type': 'application/json' },
})

const res = await makeFetch(handler)('/')

run()
res.expectBody({ hello: 'world' })
```

### Requests

The first argument is a path, and the second is a regular
[`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit).

```ts
const res = await fetch('/users?limit=10', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'deno' }),
})
```

A single `fetch` can be reused for as many requests as you like.

```ts
const fetch = makeFetch(handler)

for (const path of ['/', '/status', '/header']) {
const res = await fetch(path)
res.expectStatus(200)
}
```

### Full access to Response and port

The returned object is a real `Response` with the assertion helpers and the port
attached.

```ts
import { describe, it } from 'jsr:@std/testing/bdd'
import { makeFetch } from 'jsr:@deno-libs/superfetch'
const res = await fetch('/')

describe('makeFetch', () => {
it('should work with HTTP handler', async () => {
const handler = (res) => new Response('Hello World')
res.expect('Hello World')
console.log(res.status) // 200
console.log(res.headers.get('Content-Type'))
console.log(res.port) // port the handler was served on
```

const fetch = makeFetch(s)
Note that the body has already been read in order to run the assertions, so
`res.json()` / `res.text()` cannot be called again.

const res = await fetch('/')

res.expect('Hello World')
console.log(res) // server response object
console.log(res.port) // port on which the server was listening
})
### Testing an existing server

`makeFetch` also accepts a `Deno.HttpServer`. The server is yours, so superfetch
leaves it running — shut it down when you are done.

```ts
const server = Deno.serve({ port: 0, onListen: () => {} }, handler)

const fetch = makeFetch(server)
const res = await fetch('/')

res.expectStatus(200).expectBody('Hello World')

run()
await server.shutdown()
```

[gh-actions-img]: https://img.shields.io/github/actions/workflow/status/deno-libs/superfetch/main.yml?branch=master&style=for-the-badge&logo=github
[cov]: https://coveralls.io/github/deno-libs/superfetch
[github-actions]: https://github.com/deno-libs/superfetch/actions
[cov-badge]: https://img.shields.io/coverallsCoverage/github/deno-libs/superfetch?style=for-the-badge
[docs-badge]: https://img.shields.io/github/v/release/deno-libs/superfetch?color=yellow&label=Docs&logo=deno&style=for-the-badge
[docs]: https://doc.deno.land/https/deno.land/x/superfetch/mod.ts
[docs-badge]: https://img.shields.io/jsr/v/@deno-libs/superfetch?color=yellow&label=Docs&logo=deno&style=for-the-badge
[docs]: https://jsr.io/@deno-libs/superfetch/doc
18 changes: 17 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,25 @@
"test": "deno test --allow-net --allow-read --allow-write --allow-env --coverage=coverage"
},
"name": "@deno-libs/superfetch",
"version": "3.0.0",
"version": "4.0.0",
"exports": {
".": "./mod.ts",
"./types": "./types.ts"
},
"publish": {
"exclude": [
".claude",
".github",
".vscode",
"**/*_test.ts"
]
},
"imports": {
"@deno-libs/gql": "jsr:@deno-libs/gql@^4.1.0",
"@std/assert": "jsr:@std/assert@^1.0.19",
"@std/expect": "jsr:@std/expect@^1.0.20",
"@std/media-types": "jsr:@std/media-types@^1.1.0",
"@std/testing": "jsr:@std/testing@^1.0.20",
"graphql": "npm:graphql@^16.10.0"
}
}
94 changes: 57 additions & 37 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { assertEquals } from 'jsr:@std/assert@1.0.0/equals'
export { assertMatch } from 'jsr:@std/assert@1.0.0/match'
export { parseMediaType } from 'jsr:@std/media-types@1.0.1/parse-media-type'
export { assertEquals } from '@std/assert/equals'
export { assertMatch } from '@std/assert/match'
export { parseMediaType } from '@std/media-types/parse-media-type'
6 changes: 3 additions & 3 deletions e2e_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it } from 'jsr:@std/testing@0.225.3/bdd'
import { describe, it } from '@std/testing/bdd'
import { makeFetch } from './mod.ts'
import { GraphQLHTTP } from 'https://deno.land/x/gql@3.0.1/mod.ts'
import { buildSchema } from 'npm:graphql@16.8.1'
import { GraphQLHTTP } from '@deno-libs/gql'
import { buildSchema } from 'graphql'

describe('e2e', () => {
it('should work with GraphQL', async () => {
Expand Down
22 changes: 0 additions & 22 deletions egg.json

This file was deleted.

Loading
Loading