Skip to content

Commit 646efc4

Browse files
committed
chore: more testing examples
1 parent 2d54289 commit 646efc4

File tree

16 files changed

+1767
-6
lines changed

16 files changed

+1767
-6
lines changed
Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,36 @@
1-
# 13-unit-test-mock-other-modules
1+
# 15-integration-test
22

3-
Demonstrates how to mock internal or third-party modules
3+
Demonstrates how create an integration test
4+
5+
## Dependencies
6+
7+
This example requires you to install some third-party dependencies from npm.
8+
9+
If you have `pnpm` installed, you can do that with:
10+
11+
```bash
12+
pnpm install
13+
```
14+
15+
Alternatively, if you prefer to use another package manager, make sure to delete
16+
the `pnpm-lock.yaml` file before using it.
17+
18+
If you want to use `npm`, you can run:
19+
20+
```bash
21+
npm install
22+
```
23+
24+
If you want to use `yarn`, you can run:
25+
26+
```bash
27+
yarn install
28+
```
429

530
## Run
631

732
To run the example launch:
833

934
```
10-
node --test --experimental-test-module-mocks
35+
node --test
1136
```

10-testing/15-integration-test/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "13-unit-test-mock-other-modules",
2+
"name": "15-integration-test",
33
"version": "1.0.0",
4-
"description": "Demonstrates how to mock internal or third-party modules",
4+
"description": "Demonstrates how create an integration test",
55
"type": "module",
66
"scripts": {},
77
"engines": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data/**/*.sqlite
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# 16-integration-test-http
2+
3+
Demonstrates how to create an integration test for an HTTP server using Fastify.
4+
5+
## Dependencies
6+
7+
This example requires you to install some third-party dependencies from npm.
8+
9+
If you have `pnpm` installed, you can do that with:
10+
11+
```bash
12+
pnpm install
13+
```
14+
15+
Alternatively, if you prefer to use another package manager, make sure to delete
16+
the `pnpm-lock.yaml` file before using it.
17+
18+
If you want to use `npm`, you can run:
19+
20+
```bash
21+
npm install
22+
```
23+
24+
If you want to use `yarn`, you can run:
25+
26+
```bash
27+
yarn install
28+
```
29+
30+
## Run
31+
32+
To run the example launch:
33+
34+
```
35+
node --test
36+
```
37+
38+
## Try the app
39+
40+
Start the webserver with:
41+
42+
```bash
43+
node server.js
44+
```
45+
46+
This will make the server available at `http://localhost:3000/`
47+
48+
You can now create a new event with a request like the following:
49+
50+
```bash
51+
curl \
52+
-X POST \
53+
-H 'Content-Type: application/json' \
54+
-d '{"name":"sample event", "totalSeats": 22}' \
55+
http://localhost:3000/events
56+
```
57+
58+
This should return a 201 Created response with the event id in the body, for
59+
example:
60+
61+
```json
62+
{
63+
"success":true,
64+
"eventId":"21cd8fdf-95e5-4253-9ca4-7291c63f25e7"
65+
}
66+
```
67+
68+
Now you can create reservations for the event with a request like the following
69+
(make sure to update the event id in the URL):
70+
71+
```bash
72+
curl \
73+
-X POST \
74+
-H 'Content-Type: application/json' \
75+
-d '{"userId": "John"}' \
76+
http://localhost:3000/events/21cd8fdf-95e5-4253-9ca4-7291c63f25e7/reservations
77+
```
78+
79+
If the reservation is successful, you should get a 201 Created response with the
80+
reservation id in the body, for example:
81+
82+
```json
83+
{
84+
"success": true,
85+
"reservationId": "21cd8fdf-95e5-4253-9ca4-7291c63f25e7"
86+
}
87+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Fastify from 'fastify'
2+
import { bookEventRoute } from './routes/bookEvent.js'
3+
import { createEventRoute } from './routes/createEvent.js'
4+
5+
export async function createApp(db) {
6+
const app = Fastify()
7+
app.decorate('db', db)
8+
9+
await app.register(bookEventRoute)
10+
await app.register(createEventRoute)
11+
12+
return app
13+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import assert from 'node:assert/strict'
2+
import { suite, test } from 'node:test'
3+
import { createApp } from './app.js'
4+
import { DbClient } from './dbClient.js'
5+
import { createTables } from './dbSetup.js'
6+
7+
suite('Booking integration tests', { concurrency: true }, () => {
8+
test('Reserving a seat works until full', async () => {
9+
const db = new DbClient(':memory:')
10+
await createTables(db)
11+
const app = await createApp(db)
12+
13+
const createEventResponse = await app.inject({
14+
method: 'POST',
15+
url: '/events',
16+
payload: { name: 'Event 1', totalSeats: 2 },
17+
})
18+
assert.equal(createEventResponse.statusCode, 201)
19+
const eventData = createEventResponse.json()
20+
const reserveUrl = `/events/${eventData.eventId}/reservations`
21+
22+
const res1 = await app.inject({
23+
method: 'POST',
24+
url: reserveUrl,
25+
payload: { userId: 'u1' },
26+
})
27+
assert.equal(res1.statusCode, 201)
28+
29+
const res2 = await app.inject({
30+
method: 'POST',
31+
url: reserveUrl,
32+
payload: { userId: 'u2' },
33+
})
34+
assert.equal(res2.statusCode, 201)
35+
36+
const res3 = await app.inject({
37+
method: 'POST',
38+
url: reserveUrl,
39+
payload: { userId: 'u3' },
40+
})
41+
assert.equal(res3.statusCode, 403)
42+
assert.deepEqual(await res3.json(), { error: 'Event is fully booked' })
43+
44+
await db.close()
45+
await app.close()
46+
})
47+
48+
test('Returns 404 if event does not exist', async () => {
49+
const db = new DbClient(':memory:')
50+
await createTables(db)
51+
const app = await createApp(db)
52+
53+
const res = await app.inject({
54+
method: 'POST',
55+
url: '/events/unknown/reservations',
56+
payload: { userId: 'u1' },
57+
})
58+
59+
assert.equal(res.statusCode, 404)
60+
assert.deepEqual(await res.json(), { error: 'Event not found' })
61+
62+
await db.close()
63+
await app.close()
64+
})
65+
})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { randomUUID } from 'node:crypto'
2+
3+
export async function reserveSeat(db, eventId, userId) {
4+
const [event] = await db.query('SELECT * FROM events WHERE id = ?', [eventId])
5+
if (!event) {
6+
throw new Error('Event not found')
7+
}
8+
9+
const existing = await db.query(
10+
// biome-ignore lint/nursery/noSecrets: <explanation>
11+
'SELECT COUNT(*) AS count FROM reservations WHERE eventId = ?',
12+
[eventId]
13+
)
14+
15+
if (existing[0].count >= event.totalSeats) {
16+
throw new Error('Event is fully booked')
17+
}
18+
19+
const reservationId = randomUUID()
20+
21+
await db.query(
22+
'INSERT INTO reservations (id, eventId, userId) VALUES (?, ?, ?)',
23+
[reservationId, eventId, userId]
24+
)
25+
26+
return reservationId
27+
}
28+
29+
export async function createEvent(db, name, totalSeats) {
30+
const eventId = randomUUID()
31+
await db.query('INSERT INTO events (id, name, totalSeats) VALUES (?, ?, ?)', [
32+
eventId,
33+
name,
34+
totalSeats,
35+
])
36+
return eventId
37+
}

10-testing/16-integration-test-http/data/.gitkeep

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { open } from 'sqlite'
2+
import sqlite3 from 'sqlite3'
3+
4+
export class DbClient {
5+
#dbPath
6+
#db
7+
8+
constructor(dbPath) {
9+
this.#dbPath = dbPath
10+
this.#db = null
11+
}
12+
13+
async #connect() {
14+
if (this.#db) {
15+
return this.#db
16+
}
17+
18+
this.#db = await open({
19+
filename: this.#dbPath,
20+
driver: sqlite3.Database,
21+
})
22+
23+
return this.#db
24+
}
25+
26+
async query(sql, params = {}) {
27+
const db = await this.#connect()
28+
return db.all(sql, params)
29+
}
30+
31+
async close() {
32+
if (this.#db) {
33+
await this.#db.close()
34+
this.#db = null
35+
}
36+
}
37+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export async function createTables(db) {
2+
await db.query(`
3+
CREATE TABLE IF NOT EXISTS events (
4+
id TEXT PRIMARY KEY,
5+
name TEXT NOT NULL,
6+
totalSeats INTEGER NOT NULL
7+
)
8+
`)
9+
10+
await db.query(`
11+
CREATE TABLE IF NOT EXISTS reservations (
12+
id TEXT PRIMARY KEY,
13+
eventId TEXT NOT NULL,
14+
userId TEXT NOT NULL,
15+
UNIQUE(eventId, userId)
16+
)
17+
`)
18+
}

0 commit comments

Comments
 (0)