Skip to content

Commit 408cbc0

Browse files
authored
refactor: setupTesting (#15)
1 parent a5224e2 commit 408cbc0

11 files changed

Lines changed: 166 additions & 113 deletions

File tree

README.md

Lines changed: 88 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,58 @@
11
# kysely-deno-postgres-dialect
22

33
[![ci](https://github.com/Byzanteam/kysely_deno_postgres_dialect/actions/workflows/ci.yml/badge.svg)](https://github.com/Byzanteam/kysely_deno_postgres_dialect/actions/workflows/ci.yml)
4-
[![Latest version](https://deno.land/badge/kysely_deno_postgres_dialect/version)](https://deno.land/x/kysely_deno_postgres_dialect)
4+
[![JSR](https://jsr.io/badges/@byzanteam/kysely-deno-postgres-dialect)](https://jsr.io/@byzanteam/kysely-deno-postgres-dialect)
55

66
[Kysely](https://github.com/kysely-org/kysely) dialect for PostgreSQL using the
77
[postgresjs](https://github.com/porsager/postgres) client.
88

99
## 🚀 Getting started
1010

11-
Import using `imports` in `deno.json`
11+
### Install dependencies
12+
13+
```
14+
deno add @byzanteam/kysely-deno-postgres-dialect
15+
```
16+
17+
Optional: Import using `imports` in `deno.json`, if you dont want to use `npm`
18+
registry.
1219

1320
```json
1421
{
1522
"imports": {
16-
"kysely-deno-postgres-dialect/": "https://deno.land/x/kysely_postgrs_js_dialect/",
23+
"kysely-deno-postgres-dialect": "jsr:@byzanteam/kysely-deno-postgres-dialect",
1724
"postgresjs": "https://deno.land/x/postgresjs@v3.4.4/mod.js",
1825
"kysely": "https://esm.sh/kysely@0.27.3"
1926
}
2027
}
2128
```
2229

23-
use kysely-deno-postgres-dialect
30+
### Use kysely-deno-postgres-dialect
31+
32+
1. Define your database schema
2433

2534
```typescript
35+
import postgres from "postgresjs/mod.js";
36+
import { type Generated, Kysely } from "kysely";
2637
import {
2738
PostgresJSDialect,
2839
setup,
2940
wrapTransaction as wrapTransactionFn,
30-
} from "kysely-deno-postgres-dialect/mod.ts";
31-
import postgres from "postgresjs/mod.js";
41+
} from "kysely-deno-postgres-dialect";
42+
43+
interface UserTable {
44+
id: Generated<number>;
45+
name: string;
46+
}
47+
48+
interface Database {
49+
users: UserTable;
50+
}
51+
```
52+
53+
2. Setup the kysely instance before using it
3254

55+
```typescript
3356
setup(() => {
3457
const dialect = new PostgresJSDialect({
3558
postgres: postgres(
@@ -44,81 +67,90 @@ setup(() => {
4467
),
4568
});
4669

47-
return new Kysely<Database>({ // Database is defined by Kysely orm
70+
return new Kysely<Database>({
4871
dialect,
4972
});
5073
});
74+
```
5175

76+
3. Make your own `wrapTransaction` function that incorporates a database context
77+
78+
```typescript
5279
async function wrapTransaction<T>(
5380
callback: Parameters<typeof wrapTransactionFn<Database, T>>[0],
5481
): Promise<T> {
5582
return await wrapTransactionFn<Database, T>(callback);
5683
}
84+
```
5785

58-
const data = await wrapTransaction(async (trx) => {
59-
return trx.selectFrom("hello").selectAll().execute();
86+
4. Use the `wrapTransaction` function to execute queries
87+
88+
```typescript
89+
const users = await wrapTransaction(async (trx) => {
90+
return await trx.selectFrom("users").selectAll().execute();
6091
});
6192
```
6293

6394
## 🩺 Testing
6495

65-
See detail at `./tests/testing/utils_test.ts`.
96+
> [!TIP]\
97+
> See examples at `./tests/testing/utils_test.ts`.
6698
67-
> [!IMPORTANT]\
68-
> To fix `leaking resources` error, you should end all connections between
69-
> cases.
99+
`setupTesting` function is provided to set up the testing environment. It stubs
100+
transaction functions, and each test runs in a transaction that is rolled back
101+
after the test. Theoretically, tests are isolated from each other, and can be
102+
run in parallel.
70103

71-
```typescript
72-
import {
73-
PostgresJSDialect,
74-
setup,
75-
wrapTransaction,
76-
} from "https://deno.land/x/kysely_deno_postgres_dialect/mod.ts";
77-
import { setupTesting } from "https://deno.land/x/kysely_deno_postgres_dialect/testing.ts";
104+
1. Add a helper function to setup the testing database
78105

79-
setup(() => {
80-
const dialect = new PostgresJSDialect({
81-
postgres: postgres(
82-
{
83-
database: "postgres",
84-
hostname: "localhost",
85-
password: "postgres",
86-
port: 5432,
87-
user: "postgres",
88-
max: 10,
89-
},
90-
),
106+
```typescript
107+
// test_helper.ts
108+
109+
import { PostgresJSDialect, setup } from "kysely-deno-postgres-dialect";
110+
import { Kysely } from "kysely";
111+
import postgres from "postgresjs";
112+
113+
export function setupTestingDB() {
114+
setup(() => {
115+
const dialect = new PostgresJSDialect({
116+
postgres: postgres(
117+
{
118+
database: "postgres",
119+
hostname: "localhost",
120+
password: "postgres",
121+
port: 5432,
122+
user: "postgres",
123+
max: 10,
124+
},
125+
),
126+
});
127+
128+
return new Kysely<Database>({
129+
dialect,
130+
});
91131
});
132+
}
133+
```
92134

93-
return new Kysely<Database>({
94-
dialect,
95-
});
96-
});
135+
2. Setup the testing environment in the test files, and write tests
97136

137+
```typescript
98138
// test files
99139

100-
const {
101-
beforeAllFn,
102-
beforeEachFn,
103-
afterEachFn,
104-
afterAllFn,
105-
} = setupTesting(stub);
140+
import { setupTesting } from "kysely-deno-postgres-dialect/testing";
141+
import { stub } from "jsr:@std/testing/mock";
142+
import {
143+
afterAll,
144+
afterEach,
145+
beforeAll,
146+
beforeEach,
147+
describe,
148+
it,
149+
} from "jsr:@std/testing/bdd";
106150

107-
describe("tests", () => {
108-
beforeAll(async () => {
109-
await beforeAllFn();
110-
});
111-
afterAll(async () => {
112-
await afterAllFn();
113-
});
114-
beforeEach(async () => {
115-
await beforeEachFn();
116-
});
117-
afterEach(async () => {
118-
// note: fix Leaking resources error
119-
await afterEachFn();
120-
});
151+
setupTesting({ stub, beforeEach, afterEach, beforeAll, afterAll });
121152

153+
describe("this is a description", () => {
122154
it("works", async () => {
123155
// snip
124156
});

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json",
33
"name": "@byzanteam/kysely-deno-postgres-dialect",
4-
"version": "0.27.6",
4+
"version": "0.27.7",
55
"exports": {
66
".": "./mod.ts",
77
"./testing": "./testing.ts"

mod.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
export { type DatabaseConnection, sql } from "kysely";
21
export {
32
PostgresJSConnection,
43
PostgresJSDialect,

src/connection.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
import type postgres from "postgresjs";
88
import { PostgresJSDialectError } from "./errors.ts";
99

10+
/**
11+
* A database connection that uses the `postgresjs` library.
12+
*/
1013
export class PostgresJSConnection implements DatabaseConnection {
1114
#reservedConnection: postgres.ReservedSql;
1215

src/dialect.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { PostgresJSDriver } from "./driver.ts";
1313
import type { PostgresJSDialectConfig } from "./types.ts";
1414
import { freeze } from "./postgres-utils.ts";
1515

16+
/**
17+
* A kysely dialect for PostgreSQL using the `postgres` library.
18+
*/
1619
export class PostgresJSDialect implements Dialect {
1720
readonly #config: PostgresJSDialectConfig;
1821

src/errors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* Custom error class for PostgresJSDialect
3+
*/
14
export class PostgresJSDialectError extends Error {
25
constructor(message: string) {
36
super(message);

src/testing/utils.ts

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,47 @@ async function doWrapTransactionStub<T, U>(
2121
}
2222

2323
type Stub = { restore: () => unknown };
24+
type beforeAll<T> = (fn: (this: T) => void | Promise<void>) => void;
25+
type afterAll<T> = (fn: (this: T) => void | Promise<void>) => void;
26+
type beforeEach<T> = (fn: (this: T) => void | Promise<void>) => void;
27+
type afterEach<T> = (fn: (this: T) => void | Promise<void>) => void;
28+
29+
interface setupTestingOptions {
30+
stub: (...args: unknown[]) => Stub;
31+
beforeAll: beforeAll<unknown>;
32+
beforeEach: beforeEach<unknown>;
33+
afterEach: afterEach<unknown>;
34+
afterAll: afterAll<unknown>;
35+
}
2436

2537
/**
26-
* Setup a database for testing.
38+
* Setup the database for testing.
39+
* It will create a transaction for each test case and rollback the transaction after each test case.
2740
*
2841
* @param stub - The stub to use for wrapping the transaction.
29-
* @returns The `beforeAllFn`, `beforeEachFn`, `afterEachFn` and `afterAllFn` functions to use in your test suite.
42+
* @param beforeAll - The `beforeAll` function from the test suite.
43+
* @param beforeEach - The `beforeEach` function from the test suite.
44+
* @param afterEach - The `afterEach` function from the test suite.
45+
* @param afterAll - The `afterAll` function from the test suite.
46+
* @returns void
47+
*
3048
* @example
3149
* ```ts
32-
* import { stub } from "https://deno.land/std@0.210.0/testing/mock.ts";
33-
* const { beforeEachFn, afterEachFn } = setupTesting(stub)
50+
* import { stub } from "jsr:@std/testing/mock";
51+
* import bdd from "jsr:@std/testing/bdd";
52+
* setupTesting({stub, ...bdd})
3453
* ```
3554
*/
3655
export function setupTesting<T>(
37-
stub: (...args: unknown[]) => Stub,
38-
): {
39-
beforeAllFn: () => Promise<void>;
40-
beforeEachFn: () => Promise<void>;
41-
afterEachFn: () => Promise<void>;
42-
afterAllFn: () => Promise<void>;
43-
} {
56+
{ stub, beforeAll, beforeEach, afterEach, afterAll }: setupTestingOptions,
57+
): void {
4458
let transactionStub: Maybe<Stub>;
4559

46-
// deno-lint-ignore require-await
47-
const beforeAllFn = async () => {
60+
beforeAll(() => {
4861
getDB<T>();
49-
};
62+
});
5063

51-
const beforeEachFn = async () => {
64+
beforeEach(async () => {
5265
const db = getDB<T>();
5366

5467
const { trx, rollback } = await begin(db);
@@ -60,20 +73,19 @@ export function setupTesting<T>(
6073
"doWrapTransaction",
6174
doWrapTransactionStub,
6275
);
63-
};
76+
});
6477

65-
const afterEachFn = async () => {
78+
afterEach(async () => {
6679
if (transaction) {
6780
await rollbackFn?.();
6881
transaction = undefined;
6982
}
7083

7184
transactionStub?.restore();
72-
};
85+
});
7386

74-
const afterAllFn = async () => {
87+
afterAll(async () => {
88+
// To fix `leaking resources` error, close the database connection
7589
await closeDB();
76-
};
77-
78-
return { beforeAllFn, beforeEachFn, afterEachFn, afterAllFn };
90+
});
7991
}

src/utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ type SetupFn<T> = () => DB<T>;
1010
// deno-lint-ignore no-explicit-any
1111
let setupFn: Maybe<SetupFn<any>>;
1212

13+
/**
14+
* Sets up the database instance using the provided setup function.
15+
* This function must be called before any database operations are performed.
16+
* @param setup - A function that returns a configured Kysely database instance.
17+
*/
1318
export function setup<T>(setup: SetupFn<T>): void {
1419
setupFn = setup;
1520
}
@@ -23,10 +28,18 @@ function newDB<T>(): DB<T> {
2328
throw new Error("Please call setup() before using the database");
2429
}
2530

31+
/**
32+
* Get the database instance. Ensure you call `setup` before calling this function.
33+
* @returns The database instance.
34+
* @throws Will throw an error if `setup` has not been called.
35+
*/
2636
export function getDB<T>(): Kysely<T> {
2737
return db || newDB<T>();
2838
}
2939

40+
/**
41+
* Closes the database connection.
42+
*/
3043
export async function closeDB() {
3144
if (db) {
3245
await db.destroy();
@@ -40,6 +53,18 @@ async function doWrapTransaction<T, U>(callback: Callback<T, U>): Promise<U> {
4053
return await getDB<T>().transaction().execute(callback);
4154
}
4255

56+
/**
57+
* Wraps a callback in a transaction.
58+
* @param callback - The callback to wrap.
59+
* @returns The result of the callback.
60+
*
61+
* @example
62+
* ```ts
63+
* const users = await wrapTransaction(async (trx) => {
64+
* return await trx.selectFrom("user").selectAll().execute();
65+
* });
66+
* ```
67+
*/
4368
export async function wrapTransaction<T, U>(
4469
callback: Callback<T, U>,
4570
): Promise<U> {

tests/deps.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "jsr:@std/testing/bdd";
2+
export * from "jsr:@std/testing/mock";
3+
export * from "jsr:@std/assert";

0 commit comments

Comments
 (0)