@geekmidas/testkit 0.0.16 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -199
- package/dist/{KyselyFactory-DRQ83r0o.d.cts → KyselyFactory-BFygzOlO.d.cts} +21 -8
- package/dist/{KyselyFactory-Cx3sezwH.d.mts → KyselyFactory-BTdygZ-i.d.mts} +21 -8
- package/dist/{KyselyFactory-BcYkC0t2.mjs → KyselyFactory-CXY5gJk2.mjs} +25 -12
- package/dist/KyselyFactory-CXY5gJk2.mjs.map +1 -0
- package/dist/{KyselyFactory-Cf0o2YxO.cjs → KyselyFactory-DaaCykWP.cjs} +25 -12
- package/dist/KyselyFactory-DaaCykWP.cjs.map +1 -0
- package/dist/KyselyFactory.cjs +1 -1
- package/dist/KyselyFactory.d.cts +1 -1
- package/dist/KyselyFactory.d.mts +1 -1
- package/dist/KyselyFactory.mjs +1 -1
- package/dist/{ObjectionFactory-C-59Hjwj.d.mts → ObjectionFactory-BagGjikT.d.mts} +24 -11
- package/dist/{ObjectionFactory-C4X78k0B.d.cts → ObjectionFactory-CeSIN3kZ.d.cts} +24 -11
- package/dist/{ObjectionFactory-CDriunkS.cjs → ObjectionFactory-Eb04AOnv.cjs} +28 -15
- package/dist/ObjectionFactory-Eb04AOnv.cjs.map +1 -0
- package/dist/{ObjectionFactory-8hebmnai.mjs → ObjectionFactory-zf2fLKrL.mjs} +28 -15
- package/dist/ObjectionFactory-zf2fLKrL.mjs.map +1 -0
- package/dist/ObjectionFactory.cjs +1 -1
- package/dist/ObjectionFactory.d.cts +1 -1
- package/dist/ObjectionFactory.d.mts +1 -1
- package/dist/ObjectionFactory.mjs +1 -1
- package/dist/{VitestKyselyTransactionIsolator-COCVfvfr.d.mts → VitestKyselyTransactionIsolator-4HOeLQ0d.d.cts} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-Cst3vFjb.cjs → VitestKyselyTransactionIsolator-DX_VPKS-.cjs} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-Cst3vFjb.cjs.map → VitestKyselyTransactionIsolator-DX_VPKS-.cjs.map} +1 -1
- package/dist/{VitestKyselyTransactionIsolator-DYUYVEh9.d.cts → VitestKyselyTransactionIsolator-DnyZMaA-.d.mts} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-BxjlD1YM.mjs → VitestKyselyTransactionIsolator-XDL3ngs_.mjs} +2 -2
- package/dist/{VitestKyselyTransactionIsolator-BxjlD1YM.mjs.map → VitestKyselyTransactionIsolator-XDL3ngs_.mjs.map} +1 -1
- package/dist/VitestKyselyTransactionIsolator.cjs +2 -2
- package/dist/VitestKyselyTransactionIsolator.d.cts +2 -2
- package/dist/VitestKyselyTransactionIsolator.d.mts +2 -2
- package/dist/VitestKyselyTransactionIsolator.mjs +2 -2
- package/dist/{VitestObjectionTransactionIsolator-b973r9O1.d.mts → VitestObjectionTransactionIsolator-COVDlpEo.d.cts} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-DzeF4UAq.cjs → VitestObjectionTransactionIsolator-D_tlOtq8.cjs} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-DzeF4UAq.cjs.map → VitestObjectionTransactionIsolator-D_tlOtq8.cjs.map} +1 -1
- package/dist/{VitestObjectionTransactionIsolator-BU-jXEhz.mjs → VitestObjectionTransactionIsolator-_EhJKu_O.mjs} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-BU-jXEhz.mjs.map → VitestObjectionTransactionIsolator-_EhJKu_O.mjs.map} +1 -1
- package/dist/{VitestObjectionTransactionIsolator-CJ4ds5Qv.d.cts → VitestObjectionTransactionIsolator-lZUSz1w0.d.mts} +2 -2
- package/dist/VitestObjectionTransactionIsolator.cjs +2 -2
- package/dist/VitestObjectionTransactionIsolator.d.cts +2 -2
- package/dist/VitestObjectionTransactionIsolator.d.mts +2 -2
- package/dist/VitestObjectionTransactionIsolator.mjs +2 -2
- package/dist/{VitestTransactionIsolator-CskiiJbW.mjs → VitestTransactionIsolator-BIaMs4c2.mjs} +40 -2
- package/dist/VitestTransactionIsolator-BIaMs4c2.mjs.map +1 -0
- package/dist/{VitestTransactionIsolator-BQ5FpLtC.cjs → VitestTransactionIsolator-BKIrj3Uy.cjs} +45 -1
- package/dist/VitestTransactionIsolator-BKIrj3Uy.cjs.map +1 -0
- package/dist/{VitestTransactionIsolator-CsfJBxcb.d.mts → VitestTransactionIsolator-CyG_i_Nj.d.cts} +61 -3
- package/dist/{VitestTransactionIsolator-DdLNODZg.d.cts → VitestTransactionIsolator-DWDbnITQ.d.mts} +61 -3
- package/dist/VitestTransactionIsolator.cjs +3 -2
- package/dist/VitestTransactionIsolator.d.cts +2 -2
- package/dist/VitestTransactionIsolator.d.mts +2 -2
- package/dist/VitestTransactionIsolator.mjs +2 -2
- package/dist/better-auth.cjs +242 -0
- package/dist/better-auth.cjs.map +1 -0
- package/dist/better-auth.d.cts +17 -0
- package/dist/better-auth.d.mts +17 -0
- package/dist/better-auth.mjs +241 -0
- package/dist/better-auth.mjs.map +1 -0
- package/dist/{directory-B4oYx02C.d.mts → directory-BXavAeJZ.d.mts} +3 -3
- package/dist/{directory-BUcnztHI.d.cts → directory-DlkPEzL4.d.cts} +3 -3
- package/dist/kysely.cjs +58 -4
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +58 -5
- package/dist/kysely.d.mts +58 -5
- package/dist/kysely.mjs +57 -5
- package/dist/kysely.mjs.map +1 -1
- package/dist/objection.cjs +54 -4
- package/dist/objection.cjs.map +1 -1
- package/dist/objection.d.cts +54 -5
- package/dist/objection.d.mts +54 -5
- package/dist/objection.mjs +53 -5
- package/dist/objection.mjs.map +1 -1
- package/dist/os/directory.d.cts +1 -1
- package/dist/os/directory.d.mts +1 -1
- package/dist/os/index.d.cts +1 -1
- package/dist/os/index.d.mts +1 -1
- package/package.json +13 -3
- package/src/KyselyFactory.ts +29 -16
- package/src/ObjectionFactory.ts +34 -19
- package/src/VitestTransactionIsolator.ts +110 -2
- package/src/__tests__/KyselyFactory.spec.ts +10 -10
- package/src/__tests__/ObjectionFactory.spec.ts +9 -12
- package/src/__tests__/better-auth.spec.ts +21 -0
- package/src/__tests__/integration.spec.ts +171 -14
- package/src/better-auth.ts +325 -0
- package/src/kysely.ts +66 -0
- package/src/objection.ts +61 -0
- package/dist/KyselyFactory-BcYkC0t2.mjs.map +0 -1
- package/dist/KyselyFactory-Cf0o2YxO.cjs.map +0 -1
- package/dist/ObjectionFactory-8hebmnai.mjs.map +0 -1
- package/dist/ObjectionFactory-CDriunkS.cjs.map +0 -1
- package/dist/VitestTransactionIsolator-BQ5FpLtC.cjs.map +0 -1
- package/dist/VitestTransactionIsolator-CskiiJbW.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -6,20 +6,21 @@
|
|
|
6
6
|
[](https://www.typescriptlang.org)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Overview
|
|
10
10
|
|
|
11
11
|
**@geekmidas/testkit** provides a comprehensive set of testing utilities designed to simplify database testing in TypeScript applications. It offers factory patterns for creating test data, supports multiple database libraries, and ensures type safety throughout your tests.
|
|
12
12
|
|
|
13
13
|
### Key Features
|
|
14
14
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
15
|
+
- **Factory Pattern**: Create test data with minimal boilerplate
|
|
16
|
+
- **Type Safety**: Full TypeScript support with automatic schema inference
|
|
17
|
+
- **Multi-Database Support**: Works with Kysely and Objection.js
|
|
18
|
+
- **Transaction Isolation**: Built-in support for test isolation
|
|
19
|
+
- **Enhanced Faker**: Extended faker with timestamps, sequences, and coordinates
|
|
20
|
+
- **AWS Mocks**: Mock Lambda contexts and API Gateway events
|
|
21
|
+
- **Better Auth**: In-memory adapter for authentication testing
|
|
21
22
|
|
|
22
|
-
##
|
|
23
|
+
## Installation
|
|
23
24
|
|
|
24
25
|
```bash
|
|
25
26
|
npm install --save-dev @geekmidas/testkit
|
|
@@ -29,9 +30,22 @@ pnpm add -D @geekmidas/testkit
|
|
|
29
30
|
yarn add -D @geekmidas/testkit
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
##
|
|
33
|
+
## Subpath Exports
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
```typescript
|
|
36
|
+
import { KyselyFactory } from '@geekmidas/testkit/kysely';
|
|
37
|
+
import { ObjectionFactory } from '@geekmidas/testkit/objection';
|
|
38
|
+
import { faker } from '@geekmidas/testkit/faker';
|
|
39
|
+
import { waitFor } from '@geekmidas/testkit/timer';
|
|
40
|
+
import { itWithDir } from '@geekmidas/testkit/os';
|
|
41
|
+
import { createMockContext, createMockV1Event, createMockV2Event } from '@geekmidas/testkit/aws';
|
|
42
|
+
import { createMockLogger } from '@geekmidas/testkit/logger';
|
|
43
|
+
import { memoryAdapter } from '@geekmidas/testkit/better-auth';
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
|
|
48
|
+
### Database Factories with Kysely
|
|
35
49
|
|
|
36
50
|
```typescript
|
|
37
51
|
import { KyselyFactory } from '@geekmidas/testkit/kysely';
|
|
@@ -40,41 +54,43 @@ import { Kysely } from 'kysely';
|
|
|
40
54
|
// Define your database schema
|
|
41
55
|
interface Database {
|
|
42
56
|
users: {
|
|
43
|
-
id:
|
|
57
|
+
id: string;
|
|
44
58
|
name: string;
|
|
45
59
|
email: string;
|
|
46
60
|
createdAt: Date;
|
|
47
61
|
};
|
|
48
62
|
posts: {
|
|
49
|
-
id:
|
|
63
|
+
id: string;
|
|
50
64
|
title: string;
|
|
51
65
|
content: string;
|
|
52
|
-
userId:
|
|
53
|
-
publishedAt: Date | null;
|
|
66
|
+
userId: string;
|
|
54
67
|
};
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
// Create builders for your tables
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const builders = {
|
|
72
|
+
user: KyselyFactory.createBuilder<Database, 'users'>(
|
|
73
|
+
'users',
|
|
74
|
+
(attrs, factory, db, faker) => ({
|
|
75
|
+
id: faker.string.uuid(),
|
|
76
|
+
name: faker.person.fullName(),
|
|
77
|
+
email: faker.internet.email(),
|
|
78
|
+
createdAt: new Date(),
|
|
79
|
+
...attrs,
|
|
80
|
+
})
|
|
81
|
+
),
|
|
82
|
+
post: KyselyFactory.createBuilder<Database, 'posts'>(
|
|
83
|
+
'posts',
|
|
84
|
+
(attrs, factory, db, faker) => ({
|
|
85
|
+
id: faker.string.uuid(),
|
|
86
|
+
title: 'Test Post',
|
|
87
|
+
content: faker.lorem.paragraph(),
|
|
88
|
+
...attrs,
|
|
89
|
+
})
|
|
90
|
+
),
|
|
91
|
+
};
|
|
75
92
|
|
|
76
93
|
// Initialize factory
|
|
77
|
-
const builders = { user: userBuilder, post: postBuilder };
|
|
78
94
|
const factory = new KyselyFactory(builders, {}, db);
|
|
79
95
|
|
|
80
96
|
// Use in tests
|
|
@@ -101,124 +117,200 @@ describe('User Service', () => {
|
|
|
101
117
|
import { ObjectionFactory } from '@geekmidas/testkit/objection';
|
|
102
118
|
import { Model } from 'objection';
|
|
103
119
|
|
|
104
|
-
// Define your models
|
|
105
120
|
class User extends Model {
|
|
106
121
|
static tableName = 'users';
|
|
107
|
-
id!:
|
|
122
|
+
id!: string;
|
|
108
123
|
name!: string;
|
|
109
124
|
email!: string;
|
|
110
125
|
}
|
|
111
126
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
model: User,
|
|
123
|
-
defaults: async () => ({
|
|
124
|
-
name: 'John Doe',
|
|
125
|
-
email: `user${Date.now()}@example.com`,
|
|
126
|
-
}),
|
|
127
|
+
const builders = {
|
|
128
|
+
user: ObjectionFactory.createBuilder(
|
|
129
|
+
User,
|
|
130
|
+
(attrs, factory, db, faker) => ({
|
|
131
|
+
id: faker.string.uuid(),
|
|
132
|
+
name: faker.person.fullName(),
|
|
133
|
+
email: faker.internet.email(),
|
|
134
|
+
...attrs,
|
|
135
|
+
})
|
|
136
|
+
),
|
|
127
137
|
};
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
const factory = new ObjectionFactory({ user: userBuilder }, {});
|
|
139
|
+
const factory = new ObjectionFactory(builders, {}, knex);
|
|
131
140
|
const user = await factory.insert('user', { name: 'Jane Doe' });
|
|
132
141
|
```
|
|
133
142
|
|
|
134
|
-
##
|
|
143
|
+
## Enhanced Faker
|
|
144
|
+
|
|
145
|
+
The testkit provides an enhanced faker instance with additional utilities for common test data patterns.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { faker } from '@geekmidas/testkit/faker';
|
|
149
|
+
|
|
150
|
+
// Standard faker methods
|
|
151
|
+
const name = faker.person.fullName();
|
|
152
|
+
const email = faker.internet.email();
|
|
153
|
+
|
|
154
|
+
// Generate timestamps for database records
|
|
155
|
+
const { createdAt, updatedAt } = faker.timestamps();
|
|
156
|
+
// createdAt: Date in the past
|
|
157
|
+
// updatedAt: Date between createdAt and now
|
|
158
|
+
|
|
159
|
+
// Sequential numbers (useful for unique IDs)
|
|
160
|
+
faker.sequence(); // 1
|
|
161
|
+
faker.sequence(); // 2
|
|
162
|
+
faker.sequence('user'); // 1 (separate sequence)
|
|
163
|
+
faker.sequence('user'); // 2
|
|
164
|
+
|
|
165
|
+
// Reset sequences between tests
|
|
166
|
+
faker.resetSequence('user');
|
|
167
|
+
faker.resetAllSequences();
|
|
168
|
+
|
|
169
|
+
// Generate prices as numbers
|
|
170
|
+
const price = faker.price(); // 29.99
|
|
171
|
+
|
|
172
|
+
// Generate reverse domain identifiers
|
|
173
|
+
faker.identifier(); // "com.example.widget1"
|
|
174
|
+
faker.identifier('user'); // "org.acme.user"
|
|
175
|
+
|
|
176
|
+
// Generate coordinates within/outside a radius
|
|
177
|
+
const center = { lat: 40.7128, lng: -74.0060 };
|
|
178
|
+
faker.coordinates.within(center, 1000); // Within 1km
|
|
179
|
+
faker.coordinates.outside(center, 1000, 5000); // Between 1km and 5km
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Timer Utilities
|
|
183
|
+
|
|
184
|
+
Simple async wait utility for tests.
|
|
135
185
|
|
|
136
|
-
|
|
186
|
+
```typescript
|
|
187
|
+
import { waitFor } from '@geekmidas/testkit/timer';
|
|
188
|
+
|
|
189
|
+
it('should process after delay', async () => {
|
|
190
|
+
startBackgroundProcess();
|
|
191
|
+
await waitFor(100); // Wait 100ms
|
|
192
|
+
expect(processComplete).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
```
|
|
137
195
|
|
|
138
|
-
|
|
196
|
+
## OS Utilities
|
|
139
197
|
|
|
140
|
-
|
|
141
|
-
- **Default values**: Function returning default attributes
|
|
142
|
-
- **Transformations**: Optional data transformations before insertion
|
|
143
|
-
- **Relations**: Optional related data to create after insertion
|
|
198
|
+
Vitest fixture for temporary directory creation with automatic cleanup.
|
|
144
199
|
|
|
145
200
|
```typescript
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
email: data.email.toLowerCase(),
|
|
157
|
-
}),
|
|
158
|
-
relations: async (user, factory) => {
|
|
159
|
-
// Create related data after user insertion
|
|
160
|
-
await factory.insert('profile', { userId: user.id });
|
|
161
|
-
},
|
|
201
|
+
import { itWithDir } from '@geekmidas/testkit/os';
|
|
202
|
+
|
|
203
|
+
// Creates a temp directory before test, removes it after
|
|
204
|
+
itWithDir('should write files to temp dir', async ({ dir }) => {
|
|
205
|
+
const filePath = path.join(dir, 'test.txt');
|
|
206
|
+
await fs.writeFile(filePath, 'hello');
|
|
207
|
+
|
|
208
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
209
|
+
expect(content).toBe('hello');
|
|
210
|
+
// Directory is automatically cleaned up after test
|
|
162
211
|
});
|
|
163
212
|
```
|
|
164
213
|
|
|
165
|
-
|
|
214
|
+
## AWS Testing Utilities
|
|
166
215
|
|
|
167
|
-
|
|
216
|
+
Mock AWS Lambda contexts and API Gateway events for testing Lambda handlers.
|
|
168
217
|
|
|
169
218
|
```typescript
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
219
|
+
import {
|
|
220
|
+
createMockContext,
|
|
221
|
+
createMockV1Event,
|
|
222
|
+
createMockV2Event
|
|
223
|
+
} from '@geekmidas/testkit/aws';
|
|
224
|
+
|
|
225
|
+
describe('Lambda Handler', () => {
|
|
226
|
+
it('should handle API Gateway v1 event', async () => {
|
|
227
|
+
const event = createMockV1Event({
|
|
228
|
+
httpMethod: 'POST',
|
|
229
|
+
path: '/users',
|
|
230
|
+
body: JSON.stringify({ name: 'John' }),
|
|
231
|
+
});
|
|
232
|
+
const context = createMockContext();
|
|
233
|
+
|
|
234
|
+
const result = await handler(event, context);
|
|
235
|
+
expect(result.statusCode).toBe(201);
|
|
174
236
|
});
|
|
175
237
|
|
|
176
|
-
|
|
238
|
+
it('should handle API Gateway v2 event', async () => {
|
|
239
|
+
const event = createMockV2Event({
|
|
240
|
+
routeKey: 'POST /users',
|
|
241
|
+
rawPath: '/users',
|
|
242
|
+
body: JSON.stringify({ name: 'John' }),
|
|
243
|
+
});
|
|
244
|
+
const context = createMockContext();
|
|
177
245
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
246
|
+
const result = await handler(event, context);
|
|
247
|
+
expect(result.statusCode).toBe(201);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
```
|
|
183
251
|
|
|
184
|
-
|
|
185
|
-
};
|
|
252
|
+
## Logger Testing Utilities
|
|
186
253
|
|
|
187
|
-
|
|
188
|
-
|
|
254
|
+
Create mock loggers for testing code that uses `@geekmidas/logger`.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { createMockLogger } from '@geekmidas/testkit/logger';
|
|
258
|
+
|
|
259
|
+
describe('Service', () => {
|
|
260
|
+
it('should log errors', async () => {
|
|
261
|
+
const logger = createMockLogger();
|
|
262
|
+
const service = new MyService(logger);
|
|
263
|
+
|
|
264
|
+
await service.doSomethingRisky();
|
|
265
|
+
|
|
266
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
267
|
+
expect.objectContaining({ error: expect.any(Error) }),
|
|
268
|
+
'Operation failed'
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
189
272
|
```
|
|
190
273
|
|
|
191
|
-
|
|
274
|
+
## Better Auth Testing
|
|
192
275
|
|
|
193
|
-
|
|
276
|
+
In-memory adapter for testing Better Auth without a real database.
|
|
194
277
|
|
|
195
278
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
279
|
+
import { memoryAdapter } from '@geekmidas/testkit/better-auth';
|
|
280
|
+
import { betterAuth } from 'better-auth';
|
|
281
|
+
|
|
282
|
+
describe('Authentication', () => {
|
|
283
|
+
const adapter = memoryAdapter({
|
|
284
|
+
debugLogs: false,
|
|
285
|
+
initialData: {
|
|
286
|
+
user: [{ id: '1', email: 'test@example.com', name: 'Test User' }],
|
|
287
|
+
},
|
|
288
|
+
});
|
|
199
289
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
290
|
+
const auth = betterAuth({
|
|
291
|
+
database: adapter,
|
|
292
|
+
// ... other config
|
|
203
293
|
});
|
|
204
294
|
|
|
205
|
-
afterEach(
|
|
206
|
-
|
|
295
|
+
afterEach(() => {
|
|
296
|
+
adapter.clear(); // Reset data between tests
|
|
207
297
|
});
|
|
208
298
|
|
|
209
|
-
it('should
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
299
|
+
it('should create user', async () => {
|
|
300
|
+
await auth.api.signUp({
|
|
301
|
+
email: 'new@example.com',
|
|
302
|
+
password: 'password123',
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const data = adapter.getAllData();
|
|
306
|
+
expect(data.user).toHaveLength(2);
|
|
213
307
|
});
|
|
214
308
|
});
|
|
215
309
|
```
|
|
216
310
|
|
|
217
|
-
##
|
|
311
|
+
## Database Migration
|
|
218
312
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
TestKit includes utilities for managing test database migrations:
|
|
313
|
+
TestKit includes utilities for managing test database migrations.
|
|
222
314
|
|
|
223
315
|
```typescript
|
|
224
316
|
import { PostgresKyselyMigrator } from '@geekmidas/testkit/kysely';
|
|
@@ -237,147 +329,158 @@ const migrator = new PostgresKyselyMigrator({
|
|
|
237
329
|
// In test setup
|
|
238
330
|
beforeAll(async () => {
|
|
239
331
|
const cleanup = await migrator.start();
|
|
240
|
-
// Database is created and migrations are run
|
|
241
|
-
|
|
242
|
-
// Store cleanup function for later
|
|
243
332
|
globalThis.cleanupDb = cleanup;
|
|
244
333
|
});
|
|
245
334
|
|
|
246
335
|
afterAll(async () => {
|
|
247
336
|
await globalThis.cleanupDb?.();
|
|
248
|
-
// Database is dropped
|
|
249
337
|
});
|
|
250
338
|
```
|
|
251
339
|
|
|
252
|
-
|
|
340
|
+
## Transaction Isolation
|
|
253
341
|
|
|
254
|
-
|
|
342
|
+
TestKit supports transaction-based test isolation:
|
|
255
343
|
|
|
256
344
|
```typescript
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
async performInsert(table: string, data: any) {
|
|
261
|
-
const collection = this.db.collection(table);
|
|
262
|
-
const result = await collection.insertOne(data);
|
|
263
|
-
return { ...data, _id: result.insertedId };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async performInsertMany(table: string, data: any[]) {
|
|
267
|
-
const collection = this.db.collection(table);
|
|
268
|
-
const result = await collection.insertMany(data);
|
|
269
|
-
return data.map((item, index) => ({
|
|
270
|
-
...item,
|
|
271
|
-
_id: result.insertedIds[index],
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
```
|
|
345
|
+
describe('User Service', () => {
|
|
346
|
+
let trx: Transaction<Database>;
|
|
347
|
+
let factory: KyselyFactory;
|
|
276
348
|
|
|
277
|
-
|
|
349
|
+
beforeEach(async () => {
|
|
350
|
+
trx = await db.transaction();
|
|
351
|
+
factory = new KyselyFactory(builders, seeds, trx);
|
|
352
|
+
});
|
|
278
353
|
|
|
279
|
-
|
|
354
|
+
afterEach(async () => {
|
|
355
|
+
await trx.rollback();
|
|
356
|
+
});
|
|
280
357
|
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}));
|
|
358
|
+
it('should perform operations in isolation', async () => {
|
|
359
|
+
const user = await factory.insert('user');
|
|
360
|
+
// All changes will be rolled back after the test
|
|
361
|
+
});
|
|
362
|
+
});
|
|
287
363
|
```
|
|
288
364
|
|
|
289
|
-
|
|
365
|
+
## Seeds
|
|
290
366
|
|
|
291
|
-
|
|
367
|
+
Seeds are functions that create complex test scenarios:
|
|
292
368
|
|
|
293
369
|
```typescript
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
370
|
+
const blogSeed = async (factory: Factory) => {
|
|
371
|
+
const author = await factory.insert('user', {
|
|
372
|
+
name: 'Blog Author',
|
|
373
|
+
role: 'author',
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const categories = await factory.insertMany(3, 'category');
|
|
377
|
+
|
|
378
|
+
const posts = await factory.insertMany(5, 'post', (index) => ({
|
|
379
|
+
title: `Post ${index + 1}`,
|
|
380
|
+
authorId: author.id,
|
|
381
|
+
categoryId: categories[index % categories.length].id,
|
|
382
|
+
}));
|
|
383
|
+
|
|
384
|
+
return { author, categories, posts };
|
|
385
|
+
};
|
|
302
386
|
|
|
303
|
-
//
|
|
304
|
-
const
|
|
305
|
-
// Perform validation or modifications...
|
|
306
|
-
const post = await db.insertInto('posts').values(draftData).execute();
|
|
387
|
+
// Use in tests
|
|
388
|
+
const data = await factory.seed('blog');
|
|
307
389
|
```
|
|
308
390
|
|
|
309
|
-
##
|
|
391
|
+
## API Reference
|
|
310
392
|
|
|
311
393
|
### KyselyFactory
|
|
312
394
|
|
|
313
395
|
```typescript
|
|
314
|
-
class KyselyFactory<
|
|
396
|
+
class KyselyFactory<DB, Builders, Seeds> {
|
|
315
397
|
constructor(
|
|
316
|
-
builders:
|
|
317
|
-
seeds:
|
|
318
|
-
db: Kysely<
|
|
398
|
+
builders: Builders,
|
|
399
|
+
seeds: Seeds,
|
|
400
|
+
db: Kysely<DB> | ControlledTransaction<DB>
|
|
319
401
|
);
|
|
320
402
|
|
|
321
|
-
static createBuilder<
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
403
|
+
static createBuilder<DB, TableName extends keyof DB & string>(
|
|
404
|
+
table: TableName,
|
|
405
|
+
item?: (
|
|
406
|
+
attrs: Partial<Insertable<DB[TableName]>>,
|
|
407
|
+
factory: KyselyFactory,
|
|
408
|
+
db: Kysely<DB>,
|
|
409
|
+
faker: FakerFactory
|
|
410
|
+
) => Partial<Insertable<DB[TableName]>> | Promise<...>,
|
|
411
|
+
autoInsert?: boolean
|
|
412
|
+
): BuilderFunction;
|
|
413
|
+
|
|
414
|
+
insert<K extends keyof Builders>(
|
|
415
|
+
builderName: K,
|
|
416
|
+
attrs?: Partial<BuilderAttrs>
|
|
417
|
+
): Promise<BuilderResult>;
|
|
418
|
+
|
|
419
|
+
insertMany<K extends keyof Builders>(
|
|
331
420
|
count: number,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
): Promise<
|
|
335
|
-
|
|
336
|
-
seed<K extends keyof
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
): Promise<
|
|
421
|
+
builderName: K,
|
|
422
|
+
attrs?: Partial<BuilderAttrs> | ((idx: number, faker: FakerFactory) => Partial<BuilderAttrs>)
|
|
423
|
+
): Promise<BuilderResult[]>;
|
|
424
|
+
|
|
425
|
+
seed<K extends keyof Seeds>(
|
|
426
|
+
seedName: K,
|
|
427
|
+
attrs?: SeedAttrs
|
|
428
|
+
): Promise<SeedResult>;
|
|
340
429
|
}
|
|
341
430
|
```
|
|
342
431
|
|
|
343
|
-
###
|
|
432
|
+
### Enhanced Faker
|
|
344
433
|
|
|
345
434
|
```typescript
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
|
|
435
|
+
interface EnhancedFaker extends Faker {
|
|
436
|
+
timestamps(): { createdAt: Date; updatedAt: Date };
|
|
437
|
+
sequence(name?: string): number;
|
|
438
|
+
resetSequence(name?: string, value?: number): void;
|
|
439
|
+
resetAllSequences(): void;
|
|
440
|
+
identifier(suffix?: string): string;
|
|
441
|
+
price(): number;
|
|
442
|
+
coordinates: {
|
|
443
|
+
within(center: Coordinate, radiusMeters: number): Coordinate;
|
|
444
|
+
outside(center: Coordinate, minRadius: number, maxRadius: number): Coordinate;
|
|
445
|
+
};
|
|
354
446
|
}
|
|
355
447
|
```
|
|
356
448
|
|
|
357
|
-
###
|
|
449
|
+
### AWS Mocks
|
|
358
450
|
|
|
359
451
|
```typescript
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
452
|
+
function createMockContext(): Context;
|
|
453
|
+
function createMockV1Event(overrides?: Partial<APIGatewayProxyEvent>): APIGatewayProxyEvent;
|
|
454
|
+
function createMockV2Event(overrides?: Partial<APIGatewayProxyEventV2>): APIGatewayProxyEventV2;
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Memory Adapter (Better Auth)
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
function memoryAdapter(config?: {
|
|
461
|
+
debugLogs?: boolean;
|
|
462
|
+
usePlural?: boolean;
|
|
463
|
+
initialData?: Record<string, any[]>;
|
|
464
|
+
}): DatabaseAdapter & {
|
|
465
|
+
clear(): void;
|
|
466
|
+
getAllData(): Record<string, any[]>;
|
|
467
|
+
getStore(): Map<string, any>;
|
|
468
|
+
};
|
|
367
469
|
```
|
|
368
470
|
|
|
369
|
-
##
|
|
471
|
+
## Testing Best Practices
|
|
370
472
|
|
|
371
473
|
1. **Use Transactions**: Always wrap tests in transactions for isolation
|
|
372
474
|
2. **Create Minimal Data**: Only create the data necessary for each test
|
|
373
475
|
3. **Use Seeds for Complex Scenarios**: Encapsulate complex setups in seeds
|
|
374
476
|
4. **Leverage Type Safety**: Let TypeScript catch schema mismatches
|
|
375
477
|
5. **Clean Up Resources**: Always clean up database connections and transactions
|
|
478
|
+
6. **Reset Sequences**: Call `faker.resetAllSequences()` in `beforeEach` for predictable IDs
|
|
376
479
|
|
|
377
|
-
##
|
|
480
|
+
## Contributing
|
|
378
481
|
|
|
379
482
|
We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
380
483
|
|
|
381
|
-
##
|
|
484
|
+
## License
|
|
382
485
|
|
|
383
|
-
This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
|
|
486
|
+
This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
|