@geekmidas/testkit 0.0.17 → 0.1.1
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 +445 -193
- package/dist/{Factory-BZ8uMoXl.d.cts → Factory-DE3hE0WO.d.mts} +2 -2
- package/dist/{Factory-CRquB4vz.d.mts → Factory-pNV7ZQ7-.d.cts} +2 -2
- package/dist/Factory.d.cts +2 -2
- package/dist/Factory.d.mts +2 -2
- package/dist/{KyselyFactory-BDS_QqRT.d.mts → KyselyFactory-CPZTUuMB.d.cts} +23 -10
- 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-DRQ83r0o.d.cts → KyselyFactory-RAyvZ8Cj.d.mts} +23 -10
- package/dist/KyselyFactory.cjs +1 -1
- package/dist/KyselyFactory.d.cts +3 -3
- package/dist/KyselyFactory.d.mts +3 -3
- package/dist/KyselyFactory.mjs +1 -1
- package/dist/{ObjectionFactory-C3tHvX1d.d.mts → ObjectionFactory-BlBicEia.d.mts} +26 -13
- package/dist/{ObjectionFactory-C4X78k0B.d.cts → ObjectionFactory-BwpN4gMX.d.cts} +26 -13
- 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 +3 -3
- package/dist/ObjectionFactory.d.mts +3 -3
- package/dist/ObjectionFactory.mjs +1 -1
- package/dist/{VitestKyselyTransactionIsolator-COCVfvfr.d.mts → VitestKyselyTransactionIsolator-BqrZDeaT.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-DiskaURs.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-CD2ucJpH.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-CJ4ds5Qv.d.cts → VitestObjectionTransactionIsolator-DhQ8XGva.d.mts} +2 -2
- 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.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-DLdQlfZ5.d.mts} +70 -3
- package/dist/{VitestTransactionIsolator-DdLNODZg.d.cts → VitestTransactionIsolator-DfA80g2M.d.cts} +70 -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 +8 -11
- package/dist/better-auth.cjs.map +1 -1
- package/dist/better-auth.d.cts +2 -2
- package/dist/better-auth.d.mts +2 -2
- package/dist/better-auth.mjs +8 -11
- package/dist/better-auth.mjs.map +1 -1
- package/dist/{directory-B4oYx02C.d.mts → directory-Mi7tdOuD.d.cts} +3 -3
- package/dist/{directory-BUcnztHI.d.cts → directory-Q178x53k.d.mts} +3 -3
- package/dist/{faker-Cg76aFNO.d.cts → faker-BSH1EMtg.d.cts} +3 -3
- package/dist/{faker-Br8MzXil.d.mts → faker-D9gz7KjY.d.mts} +3 -3
- package/dist/faker.d.cts +1 -1
- package/dist/faker.d.mts +1 -1
- package/dist/kysely.cjs +58 -4
- package/dist/kysely.cjs.map +1 -1
- package/dist/kysely.d.cts +56 -6
- package/dist/kysely.d.mts +56 -6
- 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 +52 -6
- package/dist/objection.d.mts +52 -6
- 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 +7 -3
- package/src/KyselyFactory.ts +29 -16
- package/src/ObjectionFactory.ts +34 -19
- package/src/VitestTransactionIsolator.ts +126 -2
- package/src/__tests__/KyselyFactory.spec.ts +10 -10
- package/src/__tests__/ObjectionFactory.spec.ts +9 -12
- package/src/__tests__/integration.spec.ts +171 -14
- package/src/better-auth.ts +13 -15
- package/src/kysely.ts +71 -0
- package/src/objection.ts +66 -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,35 @@ 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
|
+
// Kysely utilities
|
|
37
|
+
import {
|
|
38
|
+
KyselyFactory,
|
|
39
|
+
wrapVitestKyselyTransaction,
|
|
40
|
+
extendWithFixtures,
|
|
41
|
+
} from '@geekmidas/testkit/kysely';
|
|
42
|
+
|
|
43
|
+
// Objection.js utilities
|
|
44
|
+
import {
|
|
45
|
+
ObjectionFactory,
|
|
46
|
+
wrapVitestObjectionTransaction,
|
|
47
|
+
extendWithFixtures,
|
|
48
|
+
} from '@geekmidas/testkit/objection';
|
|
49
|
+
|
|
50
|
+
// Other utilities
|
|
51
|
+
import { faker } from '@geekmidas/testkit/faker';
|
|
52
|
+
import { waitFor } from '@geekmidas/testkit/timer';
|
|
53
|
+
import { itWithDir } from '@geekmidas/testkit/os';
|
|
54
|
+
import { createMockContext, createMockV1Event, createMockV2Event } from '@geekmidas/testkit/aws';
|
|
55
|
+
import { createMockLogger } from '@geekmidas/testkit/logger';
|
|
56
|
+
import { memoryAdapter } from '@geekmidas/testkit/better-auth';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
### Database Factories with Kysely
|
|
35
62
|
|
|
36
63
|
```typescript
|
|
37
64
|
import { KyselyFactory } from '@geekmidas/testkit/kysely';
|
|
@@ -40,41 +67,43 @@ import { Kysely } from 'kysely';
|
|
|
40
67
|
// Define your database schema
|
|
41
68
|
interface Database {
|
|
42
69
|
users: {
|
|
43
|
-
id:
|
|
70
|
+
id: string;
|
|
44
71
|
name: string;
|
|
45
72
|
email: string;
|
|
46
73
|
createdAt: Date;
|
|
47
74
|
};
|
|
48
75
|
posts: {
|
|
49
|
-
id:
|
|
76
|
+
id: string;
|
|
50
77
|
title: string;
|
|
51
78
|
content: string;
|
|
52
|
-
userId:
|
|
53
|
-
publishedAt: Date | null;
|
|
79
|
+
userId: string;
|
|
54
80
|
};
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
// Create builders for your tables
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
const builders = {
|
|
85
|
+
user: KyselyFactory.createBuilder<Database, 'users'>(
|
|
86
|
+
'users',
|
|
87
|
+
({ attrs, faker }) => ({
|
|
88
|
+
id: faker.string.uuid(),
|
|
89
|
+
name: faker.person.fullName(),
|
|
90
|
+
email: faker.internet.email(),
|
|
91
|
+
createdAt: new Date(),
|
|
92
|
+
...attrs,
|
|
93
|
+
})
|
|
94
|
+
),
|
|
95
|
+
post: KyselyFactory.createBuilder<Database, 'posts'>(
|
|
96
|
+
'posts',
|
|
97
|
+
({ attrs, faker }) => ({
|
|
98
|
+
id: faker.string.uuid(),
|
|
99
|
+
title: 'Test Post',
|
|
100
|
+
content: faker.lorem.paragraph(),
|
|
101
|
+
...attrs,
|
|
102
|
+
})
|
|
103
|
+
),
|
|
104
|
+
};
|
|
75
105
|
|
|
76
106
|
// Initialize factory
|
|
77
|
-
const builders = { user: userBuilder, post: postBuilder };
|
|
78
107
|
const factory = new KyselyFactory(builders, {}, db);
|
|
79
108
|
|
|
80
109
|
// Use in tests
|
|
@@ -101,124 +130,200 @@ describe('User Service', () => {
|
|
|
101
130
|
import { ObjectionFactory } from '@geekmidas/testkit/objection';
|
|
102
131
|
import { Model } from 'objection';
|
|
103
132
|
|
|
104
|
-
// Define your models
|
|
105
133
|
class User extends Model {
|
|
106
134
|
static tableName = 'users';
|
|
107
|
-
id!:
|
|
135
|
+
id!: string;
|
|
108
136
|
name!: string;
|
|
109
137
|
email!: string;
|
|
110
138
|
}
|
|
111
139
|
|
|
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
|
-
}),
|
|
140
|
+
const builders = {
|
|
141
|
+
user: ObjectionFactory.createBuilder(
|
|
142
|
+
User,
|
|
143
|
+
({ attrs, faker }) => ({
|
|
144
|
+
id: faker.string.uuid(),
|
|
145
|
+
name: faker.person.fullName(),
|
|
146
|
+
email: faker.internet.email(),
|
|
147
|
+
...attrs,
|
|
148
|
+
})
|
|
149
|
+
),
|
|
127
150
|
};
|
|
128
151
|
|
|
129
|
-
|
|
130
|
-
const factory = new ObjectionFactory({ user: userBuilder }, {});
|
|
152
|
+
const factory = new ObjectionFactory(builders, {}, knex);
|
|
131
153
|
const user = await factory.insert('user', { name: 'Jane Doe' });
|
|
132
154
|
```
|
|
133
155
|
|
|
134
|
-
##
|
|
156
|
+
## Enhanced Faker
|
|
135
157
|
|
|
136
|
-
|
|
158
|
+
The testkit provides an enhanced faker instance with additional utilities for common test data patterns.
|
|
137
159
|
|
|
138
|
-
|
|
160
|
+
```typescript
|
|
161
|
+
import { faker } from '@geekmidas/testkit/faker';
|
|
162
|
+
|
|
163
|
+
// Standard faker methods
|
|
164
|
+
const name = faker.person.fullName();
|
|
165
|
+
const email = faker.internet.email();
|
|
166
|
+
|
|
167
|
+
// Generate timestamps for database records
|
|
168
|
+
const { createdAt, updatedAt } = faker.timestamps();
|
|
169
|
+
// createdAt: Date in the past
|
|
170
|
+
// updatedAt: Date between createdAt and now
|
|
171
|
+
|
|
172
|
+
// Sequential numbers (useful for unique IDs)
|
|
173
|
+
faker.sequence(); // 1
|
|
174
|
+
faker.sequence(); // 2
|
|
175
|
+
faker.sequence('user'); // 1 (separate sequence)
|
|
176
|
+
faker.sequence('user'); // 2
|
|
177
|
+
|
|
178
|
+
// Reset sequences between tests
|
|
179
|
+
faker.resetSequence('user');
|
|
180
|
+
faker.resetAllSequences();
|
|
181
|
+
|
|
182
|
+
// Generate prices as numbers
|
|
183
|
+
const price = faker.price(); // 29.99
|
|
184
|
+
|
|
185
|
+
// Generate reverse domain identifiers
|
|
186
|
+
faker.identifier(); // "com.example.widget1"
|
|
187
|
+
faker.identifier('user'); // "org.acme.user"
|
|
188
|
+
|
|
189
|
+
// Generate coordinates within/outside a radius
|
|
190
|
+
const center = { lat: 40.7128, lng: -74.0060 };
|
|
191
|
+
faker.coordinates.within(center, 1000); // Within 1km
|
|
192
|
+
faker.coordinates.outside(center, 1000, 5000); // Between 1km and 5km
|
|
193
|
+
```
|
|
139
194
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
- **Relations**: Optional related data to create after insertion
|
|
195
|
+
## Timer Utilities
|
|
196
|
+
|
|
197
|
+
Simple async wait utility for tests.
|
|
144
198
|
|
|
145
199
|
```typescript
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
createdAt: new Date(),
|
|
153
|
-
}),
|
|
154
|
-
transform: async (data) => ({
|
|
155
|
-
...data,
|
|
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
|
-
},
|
|
200
|
+
import { waitFor } from '@geekmidas/testkit/timer';
|
|
201
|
+
|
|
202
|
+
it('should process after delay', async () => {
|
|
203
|
+
startBackgroundProcess();
|
|
204
|
+
await waitFor(100); // Wait 100ms
|
|
205
|
+
expect(processComplete).toBe(true);
|
|
162
206
|
});
|
|
163
207
|
```
|
|
164
208
|
|
|
165
|
-
|
|
209
|
+
## OS Utilities
|
|
166
210
|
|
|
167
|
-
|
|
211
|
+
Vitest fixture for temporary directory creation with automatic cleanup.
|
|
168
212
|
|
|
169
213
|
```typescript
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
214
|
+
import { itWithDir } from '@geekmidas/testkit/os';
|
|
215
|
+
|
|
216
|
+
// Creates a temp directory before test, removes it after
|
|
217
|
+
itWithDir('should write files to temp dir', async ({ dir }) => {
|
|
218
|
+
const filePath = path.join(dir, 'test.txt');
|
|
219
|
+
await fs.writeFile(filePath, 'hello');
|
|
220
|
+
|
|
221
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
222
|
+
expect(content).toBe('hello');
|
|
223
|
+
// Directory is automatically cleaned up after test
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## AWS Testing Utilities
|
|
228
|
+
|
|
229
|
+
Mock AWS Lambda contexts and API Gateway events for testing Lambda handlers.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import {
|
|
233
|
+
createMockContext,
|
|
234
|
+
createMockV1Event,
|
|
235
|
+
createMockV2Event
|
|
236
|
+
} from '@geekmidas/testkit/aws';
|
|
237
|
+
|
|
238
|
+
describe('Lambda Handler', () => {
|
|
239
|
+
it('should handle API Gateway v1 event', async () => {
|
|
240
|
+
const event = createMockV1Event({
|
|
241
|
+
httpMethod: 'POST',
|
|
242
|
+
path: '/users',
|
|
243
|
+
body: JSON.stringify({ name: 'John' }),
|
|
244
|
+
});
|
|
245
|
+
const context = createMockContext();
|
|
246
|
+
|
|
247
|
+
const result = await handler(event, context);
|
|
248
|
+
expect(result.statusCode).toBe(201);
|
|
174
249
|
});
|
|
175
250
|
|
|
176
|
-
|
|
251
|
+
it('should handle API Gateway v2 event', async () => {
|
|
252
|
+
const event = createMockV2Event({
|
|
253
|
+
routeKey: 'POST /users',
|
|
254
|
+
rawPath: '/users',
|
|
255
|
+
body: JSON.stringify({ name: 'John' }),
|
|
256
|
+
});
|
|
257
|
+
const context = createMockContext();
|
|
177
258
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
259
|
+
const result = await handler(event, context);
|
|
260
|
+
expect(result.statusCode).toBe(201);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
```
|
|
183
264
|
|
|
184
|
-
|
|
185
|
-
};
|
|
265
|
+
## Logger Testing Utilities
|
|
186
266
|
|
|
187
|
-
|
|
188
|
-
|
|
267
|
+
Create mock loggers for testing code that uses `@geekmidas/logger`.
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { createMockLogger } from '@geekmidas/testkit/logger';
|
|
271
|
+
|
|
272
|
+
describe('Service', () => {
|
|
273
|
+
it('should log errors', async () => {
|
|
274
|
+
const logger = createMockLogger();
|
|
275
|
+
const service = new MyService(logger);
|
|
276
|
+
|
|
277
|
+
await service.doSomethingRisky();
|
|
278
|
+
|
|
279
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
280
|
+
expect.objectContaining({ error: expect.any(Error) }),
|
|
281
|
+
'Operation failed'
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
189
285
|
```
|
|
190
286
|
|
|
191
|
-
|
|
287
|
+
## Better Auth Testing
|
|
192
288
|
|
|
193
|
-
|
|
289
|
+
In-memory adapter for testing Better Auth without a real database.
|
|
194
290
|
|
|
195
291
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
292
|
+
import { memoryAdapter } from '@geekmidas/testkit/better-auth';
|
|
293
|
+
import { betterAuth } from 'better-auth';
|
|
294
|
+
|
|
295
|
+
describe('Authentication', () => {
|
|
296
|
+
const adapter = memoryAdapter({
|
|
297
|
+
debugLogs: false,
|
|
298
|
+
initialData: {
|
|
299
|
+
user: [{ id: '1', email: 'test@example.com', name: 'Test User' }],
|
|
300
|
+
},
|
|
301
|
+
});
|
|
199
302
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
303
|
+
const auth = betterAuth({
|
|
304
|
+
database: adapter,
|
|
305
|
+
// ... other config
|
|
203
306
|
});
|
|
204
307
|
|
|
205
|
-
afterEach(
|
|
206
|
-
|
|
308
|
+
afterEach(() => {
|
|
309
|
+
adapter.clear(); // Reset data between tests
|
|
207
310
|
});
|
|
208
311
|
|
|
209
|
-
it('should
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
312
|
+
it('should create user', async () => {
|
|
313
|
+
await auth.api.signUp({
|
|
314
|
+
email: 'new@example.com',
|
|
315
|
+
password: 'password123',
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const data = adapter.getAllData();
|
|
319
|
+
expect(data.user).toHaveLength(2);
|
|
213
320
|
});
|
|
214
321
|
});
|
|
215
322
|
```
|
|
216
323
|
|
|
217
|
-
##
|
|
218
|
-
|
|
219
|
-
### Database Migration
|
|
324
|
+
## Database Migration
|
|
220
325
|
|
|
221
|
-
TestKit includes utilities for managing test database migrations
|
|
326
|
+
TestKit includes utilities for managing test database migrations.
|
|
222
327
|
|
|
223
328
|
```typescript
|
|
224
329
|
import { PostgresKyselyMigrator } from '@geekmidas/testkit/kysely';
|
|
@@ -237,147 +342,294 @@ const migrator = new PostgresKyselyMigrator({
|
|
|
237
342
|
// In test setup
|
|
238
343
|
beforeAll(async () => {
|
|
239
344
|
const cleanup = await migrator.start();
|
|
240
|
-
// Database is created and migrations are run
|
|
241
|
-
|
|
242
|
-
// Store cleanup function for later
|
|
243
345
|
globalThis.cleanupDb = cleanup;
|
|
244
346
|
});
|
|
245
347
|
|
|
246
348
|
afterAll(async () => {
|
|
247
349
|
await globalThis.cleanupDb?.();
|
|
248
|
-
// Database is dropped
|
|
249
350
|
});
|
|
250
351
|
```
|
|
251
352
|
|
|
252
|
-
|
|
353
|
+
## Vitest Transaction Isolation
|
|
253
354
|
|
|
254
|
-
|
|
355
|
+
TestKit provides Vitest-specific helpers for automatic transaction isolation. Each test runs in a transaction that is automatically rolled back after the test completes.
|
|
356
|
+
|
|
357
|
+
### Basic Usage
|
|
255
358
|
|
|
256
359
|
```typescript
|
|
257
|
-
import {
|
|
360
|
+
import { test } from 'vitest';
|
|
361
|
+
import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
|
|
362
|
+
import { db } from './database';
|
|
363
|
+
|
|
364
|
+
// Wrap Vitest's test function with transaction support
|
|
365
|
+
const it = wrapVitestKyselyTransaction<Database>(
|
|
366
|
+
test,
|
|
367
|
+
() => db,
|
|
368
|
+
async (trx) => {
|
|
369
|
+
// Optional: Set up test tables or seed data
|
|
370
|
+
await trx.schema.createTable('users').execute();
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
// Each test gets its own transaction
|
|
375
|
+
it('should create user', async ({ trx }) => {
|
|
376
|
+
const user = await trx
|
|
377
|
+
.insertInto('users')
|
|
378
|
+
.values({ name: 'John' })
|
|
379
|
+
.returningAll()
|
|
380
|
+
.executeTakeFirst();
|
|
381
|
+
|
|
382
|
+
expect(user.name).toBe('John');
|
|
383
|
+
// Transaction is automatically rolled back after test
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Extending with Fixtures
|
|
258
388
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
389
|
+
Use `extendWithFixtures` to add factory and other fixtures to your tests:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { test } from 'vitest';
|
|
393
|
+
import {
|
|
394
|
+
wrapVitestKyselyTransaction,
|
|
395
|
+
extendWithFixtures,
|
|
396
|
+
KyselyFactory,
|
|
397
|
+
} from '@geekmidas/testkit/kysely';
|
|
398
|
+
|
|
399
|
+
// Define builders
|
|
400
|
+
const builders = {
|
|
401
|
+
user: KyselyFactory.createBuilder<Database, 'users'>('users', ({ faker }) => ({
|
|
402
|
+
name: faker.person.fullName(),
|
|
403
|
+
email: faker.internet.email(),
|
|
404
|
+
})),
|
|
405
|
+
post: KyselyFactory.createBuilder<Database, 'posts'>('posts', ({ faker }) => ({
|
|
406
|
+
title: faker.lorem.sentence(),
|
|
407
|
+
content: faker.lorem.paragraphs(),
|
|
408
|
+
})),
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// Create base test with transaction
|
|
412
|
+
const baseTest = wrapVitestKyselyTransaction<Database>(test, () => db);
|
|
413
|
+
|
|
414
|
+
// Extend with factory fixture
|
|
415
|
+
const it = extendWithFixtures<
|
|
416
|
+
Database,
|
|
417
|
+
{ factory: KyselyFactory<Database, typeof builders, {}> }
|
|
418
|
+
>(baseTest, {
|
|
419
|
+
factory: (trx) => new KyselyFactory(builders, {}, trx),
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Both trx and factory are available in tests
|
|
423
|
+
it('should create user with factory', async ({ trx, factory }) => {
|
|
424
|
+
const user = await factory.insert('user', { name: 'Jane' });
|
|
425
|
+
|
|
426
|
+
expect(user.id).toBeDefined();
|
|
427
|
+
expect(user.name).toBe('Jane');
|
|
428
|
+
|
|
429
|
+
// Verify in database
|
|
430
|
+
const found = await trx
|
|
431
|
+
.selectFrom('users')
|
|
432
|
+
.where('id', '=', user.id)
|
|
433
|
+
.selectAll()
|
|
434
|
+
.executeTakeFirst();
|
|
435
|
+
|
|
436
|
+
expect(found?.name).toBe('Jane');
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should create related records', async ({ factory }) => {
|
|
440
|
+
const user = await factory.insert('user');
|
|
441
|
+
const posts = await factory.insertMany(3, 'post', { userId: user.id });
|
|
442
|
+
|
|
443
|
+
expect(posts).toHaveLength(3);
|
|
444
|
+
expect(posts[0].userId).toBe(user.id);
|
|
445
|
+
});
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Multiple Fixtures
|
|
449
|
+
|
|
450
|
+
You can add multiple fixtures that all receive the transaction:
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
const it = extendWithFixtures<
|
|
454
|
+
Database,
|
|
455
|
+
{
|
|
456
|
+
factory: KyselyFactory<Database, typeof builders, {}>;
|
|
457
|
+
userRepo: UserRepository;
|
|
458
|
+
config: { maxUsers: number };
|
|
264
459
|
}
|
|
460
|
+
>(baseTest, {
|
|
461
|
+
factory: (trx) => new KyselyFactory(builders, {}, trx),
|
|
462
|
+
userRepo: (trx) => new UserRepository(trx),
|
|
463
|
+
config: () => ({ maxUsers: 100 }), // Fixtures can ignore trx if not needed
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should use multiple fixtures', async ({ factory, userRepo, config }) => {
|
|
467
|
+
const user = await factory.insert('user');
|
|
468
|
+
const found = await userRepo.findById(user.id);
|
|
469
|
+
expect(found).toBeDefined();
|
|
470
|
+
expect(config.maxUsers).toBe(100);
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### With Objection.js
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
import { wrapVitestObjectionTransaction, extendWithFixtures } from '@geekmidas/testkit/objection';
|
|
478
|
+
|
|
479
|
+
const baseTest = wrapVitestObjectionTransaction(test, () => knex);
|
|
265
480
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
...item,
|
|
271
|
-
_id: result.insertedIds[index],
|
|
272
|
-
}));
|
|
481
|
+
const it = extendWithFixtures<{ factory: ObjectionFactory<typeof builders, {}> }>(
|
|
482
|
+
baseTest,
|
|
483
|
+
{
|
|
484
|
+
factory: (trx) => new ObjectionFactory(builders, {}, trx),
|
|
273
485
|
}
|
|
274
|
-
|
|
486
|
+
);
|
|
275
487
|
```
|
|
276
488
|
|
|
277
|
-
|
|
489
|
+
## Manual Transaction Isolation
|
|
278
490
|
|
|
279
|
-
|
|
491
|
+
For more control, you can manage transactions manually:
|
|
280
492
|
|
|
281
493
|
```typescript
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
494
|
+
describe('User Service', () => {
|
|
495
|
+
let trx: Transaction<Database>;
|
|
496
|
+
let factory: KyselyFactory;
|
|
497
|
+
|
|
498
|
+
beforeEach(async () => {
|
|
499
|
+
trx = await db.transaction();
|
|
500
|
+
factory = new KyselyFactory(builders, seeds, trx);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
afterEach(async () => {
|
|
504
|
+
await trx.rollback();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should perform operations in isolation', async () => {
|
|
508
|
+
const user = await factory.insert('user');
|
|
509
|
+
// All changes will be rolled back after the test
|
|
510
|
+
});
|
|
511
|
+
});
|
|
287
512
|
```
|
|
288
513
|
|
|
289
|
-
|
|
514
|
+
## Seeds
|
|
290
515
|
|
|
291
|
-
|
|
516
|
+
Seeds are functions that create complex test scenarios:
|
|
292
517
|
|
|
293
518
|
```typescript
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
519
|
+
const blogSeed = async (factory: Factory) => {
|
|
520
|
+
const author = await factory.insert('user', {
|
|
521
|
+
name: 'Blog Author',
|
|
522
|
+
role: 'author',
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const categories = await factory.insertMany(3, 'category');
|
|
526
|
+
|
|
527
|
+
const posts = await factory.insertMany(5, 'post', (index) => ({
|
|
528
|
+
title: `Post ${index + 1}`,
|
|
529
|
+
authorId: author.id,
|
|
530
|
+
categoryId: categories[index % categories.length].id,
|
|
531
|
+
}));
|
|
532
|
+
|
|
533
|
+
return { author, categories, posts };
|
|
534
|
+
};
|
|
302
535
|
|
|
303
|
-
//
|
|
304
|
-
const
|
|
305
|
-
// Perform validation or modifications...
|
|
306
|
-
const post = await db.insertInto('posts').values(draftData).execute();
|
|
536
|
+
// Use in tests
|
|
537
|
+
const data = await factory.seed('blog');
|
|
307
538
|
```
|
|
308
539
|
|
|
309
|
-
##
|
|
540
|
+
## API Reference
|
|
310
541
|
|
|
311
542
|
### KyselyFactory
|
|
312
543
|
|
|
313
544
|
```typescript
|
|
314
|
-
class KyselyFactory<
|
|
545
|
+
class KyselyFactory<DB, Builders, Seeds> {
|
|
315
546
|
constructor(
|
|
316
|
-
builders:
|
|
317
|
-
seeds:
|
|
318
|
-
db: Kysely<
|
|
547
|
+
builders: Builders,
|
|
548
|
+
seeds: Seeds,
|
|
549
|
+
db: Kysely<DB> | ControlledTransaction<DB>
|
|
319
550
|
);
|
|
320
551
|
|
|
321
|
-
static createBuilder<
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
552
|
+
static createBuilder<DB, TableName extends keyof DB & string>(
|
|
553
|
+
table: TableName,
|
|
554
|
+
defaults?: (context: {
|
|
555
|
+
attrs: Partial<Insertable<DB[TableName]>>;
|
|
556
|
+
factory: KyselyFactory;
|
|
557
|
+
db: Kysely<DB>;
|
|
558
|
+
faker: FakerFactory;
|
|
559
|
+
}) => Partial<Insertable<DB[TableName]>> | Promise<...>,
|
|
560
|
+
autoInsert?: boolean
|
|
561
|
+
): BuilderFunction;
|
|
562
|
+
|
|
563
|
+
insert<K extends keyof Builders>(
|
|
564
|
+
builderName: K,
|
|
565
|
+
attrs?: Partial<BuilderAttrs>
|
|
566
|
+
): Promise<BuilderResult>;
|
|
567
|
+
|
|
568
|
+
insertMany<K extends keyof Builders>(
|
|
331
569
|
count: number,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
): Promise<
|
|
335
|
-
|
|
336
|
-
seed<K extends keyof
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
): Promise<
|
|
570
|
+
builderName: K,
|
|
571
|
+
attrs?: Partial<BuilderAttrs> | ((idx: number, faker: FakerFactory) => Partial<BuilderAttrs>)
|
|
572
|
+
): Promise<BuilderResult[]>;
|
|
573
|
+
|
|
574
|
+
seed<K extends keyof Seeds>(
|
|
575
|
+
seedName: K,
|
|
576
|
+
attrs?: SeedAttrs
|
|
577
|
+
): Promise<SeedResult>;
|
|
340
578
|
}
|
|
341
579
|
```
|
|
342
580
|
|
|
343
|
-
###
|
|
581
|
+
### Enhanced Faker
|
|
344
582
|
|
|
345
583
|
```typescript
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
|
|
584
|
+
interface EnhancedFaker extends Faker {
|
|
585
|
+
timestamps(): { createdAt: Date; updatedAt: Date };
|
|
586
|
+
sequence(name?: string): number;
|
|
587
|
+
resetSequence(name?: string, value?: number): void;
|
|
588
|
+
resetAllSequences(): void;
|
|
589
|
+
identifier(suffix?: string): string;
|
|
590
|
+
price(): number;
|
|
591
|
+
coordinates: {
|
|
592
|
+
within(center: Coordinate, radiusMeters: number): Coordinate;
|
|
593
|
+
outside(center: Coordinate, minRadius: number, maxRadius: number): Coordinate;
|
|
594
|
+
};
|
|
354
595
|
}
|
|
355
596
|
```
|
|
356
597
|
|
|
357
|
-
###
|
|
598
|
+
### AWS Mocks
|
|
358
599
|
|
|
359
600
|
```typescript
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
601
|
+
function createMockContext(): Context;
|
|
602
|
+
function createMockV1Event(overrides?: Partial<APIGatewayProxyEvent>): APIGatewayProxyEvent;
|
|
603
|
+
function createMockV2Event(overrides?: Partial<APIGatewayProxyEventV2>): APIGatewayProxyEventV2;
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Memory Adapter (Better Auth)
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
function memoryAdapter(config?: {
|
|
610
|
+
debugLogs?: boolean;
|
|
611
|
+
usePlural?: boolean;
|
|
612
|
+
initialData?: Record<string, any[]>;
|
|
613
|
+
}): DatabaseAdapter & {
|
|
614
|
+
clear(): void;
|
|
615
|
+
getAllData(): Record<string, any[]>;
|
|
616
|
+
getStore(): Map<string, any>;
|
|
617
|
+
};
|
|
367
618
|
```
|
|
368
619
|
|
|
369
|
-
##
|
|
620
|
+
## Testing Best Practices
|
|
370
621
|
|
|
371
622
|
1. **Use Transactions**: Always wrap tests in transactions for isolation
|
|
372
623
|
2. **Create Minimal Data**: Only create the data necessary for each test
|
|
373
624
|
3. **Use Seeds for Complex Scenarios**: Encapsulate complex setups in seeds
|
|
374
625
|
4. **Leverage Type Safety**: Let TypeScript catch schema mismatches
|
|
375
626
|
5. **Clean Up Resources**: Always clean up database connections and transactions
|
|
627
|
+
6. **Reset Sequences**: Call `faker.resetAllSequences()` in `beforeEach` for predictable IDs
|
|
376
628
|
|
|
377
|
-
##
|
|
629
|
+
## Contributing
|
|
378
630
|
|
|
379
631
|
We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
380
632
|
|
|
381
|
-
##
|
|
633
|
+
## License
|
|
382
634
|
|
|
383
|
-
This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
|
|
635
|
+
This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
|