@backend-master/test-utils 1.0.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 +250 -0
- package/dist/fixtures.d.ts +62 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +151 -0
- package/dist/fixtures.js.map +1 -0
- package/dist/http-test.d.ts +70 -0
- package/dist/http-test.d.ts.map +1 -0
- package/dist/http-test.js +141 -0
- package/dist/http-test.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-db.d.ts +55 -0
- package/dist/mock-db.d.ts.map +1 -0
- package/dist/mock-db.js +134 -0
- package/dist/mock-db.js.map +1 -0
- package/dist/spy.d.ts +96 -0
- package/dist/spy.d.ts.map +1 -0
- package/dist/spy.js +192 -0
- package/dist/spy.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# @backend-master/test-utils
|
|
2
|
+
|
|
3
|
+
A comprehensive testing utilities library for Node.js backend projects. Provides mocks, fixtures, spies, and assertion helpers to make testing easier and faster.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @backend-master/test-utils
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Mock Database**: In-memory database for testing without external dependencies
|
|
14
|
+
- **HTTP Helpers**: Build and assert HTTP responses easily
|
|
15
|
+
- **Fixtures**: Generate realistic test data with customizable builders
|
|
16
|
+
- **Spies & Mocks**: Track function calls and create mock objects
|
|
17
|
+
- **TypeScript Support**: Full type safety and IDE autocomplete
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Mock Database
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { MockDatabase } from '@backend-master/test-utils';
|
|
25
|
+
|
|
26
|
+
const db = new MockDatabase();
|
|
27
|
+
db.createTable('users');
|
|
28
|
+
|
|
29
|
+
const user = db.insert('users', {
|
|
30
|
+
name: 'Alice',
|
|
31
|
+
email: 'alice@example.com'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const found = db.findOne('users', { email: 'alice@example.com' });
|
|
35
|
+
expect(found).toEqual(user);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### HTTP Test Helpers
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { HttpTestBuilder, HttpResponseAssertion } from '@backend-master/test-utils';
|
|
42
|
+
|
|
43
|
+
// Create responses
|
|
44
|
+
const response = HttpTestBuilder.ok({ userId: 1, name: 'Alice' });
|
|
45
|
+
|
|
46
|
+
// Assert responses
|
|
47
|
+
HttpResponseAssertion.assertStatus(response, 200);
|
|
48
|
+
HttpResponseAssertion.assertBodyHasKey(response, 'userId');
|
|
49
|
+
HttpResponseAssertion.assertSuccess(response);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Test Fixtures
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Fixtures, FixtureBuilder } from '@backend-master/test-utils';
|
|
56
|
+
|
|
57
|
+
// Quick fixtures
|
|
58
|
+
const user = Fixtures.user({ role: 'admin' });
|
|
59
|
+
const products = Fixtures.products(10);
|
|
60
|
+
|
|
61
|
+
// Custom builder
|
|
62
|
+
const builder = new FixtureBuilder({ name: 'Test', active: true });
|
|
63
|
+
const items = builder
|
|
64
|
+
.set('name', 'Updated Name')
|
|
65
|
+
.merge({ active: false })
|
|
66
|
+
.buildMany(5);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Spies & Mocks
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Spy, MockObject, Defer } from '@backend-master/test-utils';
|
|
73
|
+
|
|
74
|
+
// Create a spy
|
|
75
|
+
const spy = Spy.create().returnValue('result');
|
|
76
|
+
spy(); // Call it
|
|
77
|
+
expect(spy.wasCalled()).toBe(true);
|
|
78
|
+
expect(spy.getCallCount()).toBe(1);
|
|
79
|
+
|
|
80
|
+
// Mock an object
|
|
81
|
+
const mockService = MockObject.create({
|
|
82
|
+
fetch: async () => ({ data: 'test' })
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
mockService.fetch();
|
|
86
|
+
expect(mockService.fetch.wasCalled()).toBe(true);
|
|
87
|
+
|
|
88
|
+
// Defer for async testing
|
|
89
|
+
const defer = new Defer();
|
|
90
|
+
defer.resolve('value');
|
|
91
|
+
await expect(defer.promise).resolves.toBe('value');
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## API Reference
|
|
95
|
+
|
|
96
|
+
### MockDatabase
|
|
97
|
+
|
|
98
|
+
#### Methods
|
|
99
|
+
|
|
100
|
+
- `createTable(name, schema?)` - Create a new table
|
|
101
|
+
- `insert(table, record)` - Insert a record
|
|
102
|
+
- `find(table, query)` - Find matching records
|
|
103
|
+
- `findOne(table, query)` - Find first matching record
|
|
104
|
+
- `update(table, query, updates)` - Update matching records
|
|
105
|
+
- `delete(table, query)` - Delete matching records
|
|
106
|
+
- `getAll(table)` - Get all records
|
|
107
|
+
- `clearTable(table)` - Clear a table
|
|
108
|
+
- `clearAll()` - Clear all tables
|
|
109
|
+
- `count(table)` - Count records in table
|
|
110
|
+
|
|
111
|
+
### HttpTestBuilder
|
|
112
|
+
|
|
113
|
+
Static methods to create HTTP responses:
|
|
114
|
+
|
|
115
|
+
- `ok(body)` - 200 response
|
|
116
|
+
- `created(body)` - 201 response
|
|
117
|
+
- `badRequest(message)` - 400 response
|
|
118
|
+
- `unauthorized(message)` - 401 response
|
|
119
|
+
- `forbidden(message)` - 403 response
|
|
120
|
+
- `notFound(message)` - 404 response
|
|
121
|
+
- `serverError(message)` - 500 response
|
|
122
|
+
- `custom(status, body, headers)` - Custom response
|
|
123
|
+
|
|
124
|
+
### HttpResponseAssertion
|
|
125
|
+
|
|
126
|
+
Methods to assert HTTP responses:
|
|
127
|
+
|
|
128
|
+
- `assertStatus(response, status)` - Check status code
|
|
129
|
+
- `assertBodyHasKey(response, key)` - Check body contains key
|
|
130
|
+
- `assertBodyMatches(response, expected)` - Check body values
|
|
131
|
+
- `assertHeaderExists(response, name)` - Check header exists
|
|
132
|
+
- `assertSuccess(response)` - Check 2xx status
|
|
133
|
+
- `assertError(response)` - Check 4xx/5xx status
|
|
134
|
+
|
|
135
|
+
### Fixtures
|
|
136
|
+
|
|
137
|
+
Pre-built fixture generators:
|
|
138
|
+
|
|
139
|
+
- `user(overrides?)` - Generate a user
|
|
140
|
+
- `users(count, overrides?)` - Generate multiple users
|
|
141
|
+
- `product(overrides?)` - Generate a product
|
|
142
|
+
- `products(count, overrides?)` - Generate multiple products
|
|
143
|
+
- `order(overrides?)` - Generate an order
|
|
144
|
+
- `orders(count, overrides?)` - Generate multiple orders
|
|
145
|
+
- `email(domain?)` - Generate a random email
|
|
146
|
+
- `randomString(length?)` - Generate random string
|
|
147
|
+
- `uuid()` - Generate a UUID
|
|
148
|
+
- `custom(data, count?)` - Create custom fixture
|
|
149
|
+
|
|
150
|
+
### FixtureBuilder<T>
|
|
151
|
+
|
|
152
|
+
Builder pattern for complex fixtures:
|
|
153
|
+
|
|
154
|
+
- `set(key, value)` - Set a property
|
|
155
|
+
- `merge(overrides)` - Merge properties
|
|
156
|
+
- `build()` - Build single fixture
|
|
157
|
+
- `buildMany(count)` - Build multiple fixtures
|
|
158
|
+
|
|
159
|
+
### Spy
|
|
160
|
+
|
|
161
|
+
Function spy for tracking calls:
|
|
162
|
+
|
|
163
|
+
- `create(implementation?)` - Create a spy
|
|
164
|
+
- `returnValue(value)` - Set return value
|
|
165
|
+
- `throwError(error)` - Make it throw
|
|
166
|
+
- `getCalls()` - Get all calls
|
|
167
|
+
- `getCallCount()` - Get call count
|
|
168
|
+
- `getArguments(index)` - Get call arguments
|
|
169
|
+
- `getFirstArg()` - Get first arg of first call
|
|
170
|
+
- `wasCalled()` - Check if called
|
|
171
|
+
- `wasCalledWith(...args)` - Check arguments
|
|
172
|
+
- `reset()` - Reset spy
|
|
173
|
+
|
|
174
|
+
### MockObject
|
|
175
|
+
|
|
176
|
+
Object mocking utilities:
|
|
177
|
+
|
|
178
|
+
- `create(shape)` - Create object with properties
|
|
179
|
+
- `createWithSpies(keys)` - Create object with spy methods
|
|
180
|
+
|
|
181
|
+
### Defer<T>
|
|
182
|
+
|
|
183
|
+
Promise deferred for testing async code:
|
|
184
|
+
|
|
185
|
+
- `promise` - The promise
|
|
186
|
+
- `resolve(value)` - Resolve the promise
|
|
187
|
+
- `reject(reason)` - Reject the promise
|
|
188
|
+
|
|
189
|
+
## Usage Examples
|
|
190
|
+
|
|
191
|
+
### Testing a User Service
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { MockDatabase, Fixtures, Spy } from '@backend-master/test-utils';
|
|
195
|
+
|
|
196
|
+
describe('UserService', () => {
|
|
197
|
+
let db: MockDatabase;
|
|
198
|
+
let emailService: any;
|
|
199
|
+
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
db = new MockDatabase();
|
|
202
|
+
db.createTable('users');
|
|
203
|
+
emailService = { send: Spy.create() };
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('should create user and send email', () => {
|
|
207
|
+
const user = Fixtures.user({ email: 'new@example.com' });
|
|
208
|
+
db.insert('users', user);
|
|
209
|
+
emailService.send();
|
|
210
|
+
|
|
211
|
+
expect(db.count('users')).toBe(1);
|
|
212
|
+
expect(emailService.send.wasCalled()).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Testing an API Endpoint
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { HttpTestBuilder, HttpResponseAssertion } from '@backend-master/test-utils';
|
|
221
|
+
|
|
222
|
+
describe('GET /api/users/:id', () => {
|
|
223
|
+
test('should return user when found', () => {
|
|
224
|
+
const response = HttpTestBuilder.ok({
|
|
225
|
+
id: 1,
|
|
226
|
+
name: 'Alice'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
HttpResponseAssertion.assertStatus(response, 200);
|
|
230
|
+
HttpResponseAssertion.assertBodyHasKey(response, 'name');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('should return 404 when not found', () => {
|
|
234
|
+
const response = HttpTestBuilder.notFound('User not found');
|
|
235
|
+
HttpResponseAssertion.assertStatus(response, 404);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Best Practices
|
|
241
|
+
|
|
242
|
+
1. **Use Fixtures for Realistic Data**: Always use fixtures instead of hardcoding test data
|
|
243
|
+
2. **Separate Concerns**: Keep database mocks, HTTP mocks, and service mocks separate
|
|
244
|
+
3. **Reset State**: Always reset spies and databases between tests
|
|
245
|
+
4. **Type Safety**: Leverage TypeScript types for better IDE support
|
|
246
|
+
5. **Readable Assertions**: Use assertion helpers instead of raw comparisons
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Fixtures & Data Generators
|
|
3
|
+
* Utilities for creating realistic test data
|
|
4
|
+
*/
|
|
5
|
+
export interface FixtureGeneratorOptions {
|
|
6
|
+
count?: number;
|
|
7
|
+
seed?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class Fixtures {
|
|
10
|
+
/**
|
|
11
|
+
* Generate a user fixture
|
|
12
|
+
*/
|
|
13
|
+
static user(overrides?: Record<string, any>): any;
|
|
14
|
+
/**
|
|
15
|
+
* Generate multiple user fixtures
|
|
16
|
+
*/
|
|
17
|
+
static users(count?: number, overrides?: Record<string, any>): any[];
|
|
18
|
+
/**
|
|
19
|
+
* Generate a product fixture
|
|
20
|
+
*/
|
|
21
|
+
static product(overrides?: Record<string, any>): any;
|
|
22
|
+
/**
|
|
23
|
+
* Generate multiple product fixtures
|
|
24
|
+
*/
|
|
25
|
+
static products(count?: number, overrides?: Record<string, any>): any[];
|
|
26
|
+
/**
|
|
27
|
+
* Generate an order fixture
|
|
28
|
+
*/
|
|
29
|
+
static order(overrides?: Record<string, any>): any;
|
|
30
|
+
/**
|
|
31
|
+
* Generate multiple order fixtures
|
|
32
|
+
*/
|
|
33
|
+
static orders(count?: number, overrides?: Record<string, any>): any[];
|
|
34
|
+
/**
|
|
35
|
+
* Generate a random email
|
|
36
|
+
*/
|
|
37
|
+
static email(domain?: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generate a random string
|
|
40
|
+
*/
|
|
41
|
+
static randomString(length?: number): string;
|
|
42
|
+
/**
|
|
43
|
+
* Generate a random UUID
|
|
44
|
+
*/
|
|
45
|
+
static uuid(): string;
|
|
46
|
+
/**
|
|
47
|
+
* Create a fixture with custom data
|
|
48
|
+
*/
|
|
49
|
+
static custom(baseData: Record<string, any>, count?: number): any | any[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Builder pattern for creating complex fixtures
|
|
53
|
+
*/
|
|
54
|
+
export declare class FixtureBuilder<T> {
|
|
55
|
+
private data;
|
|
56
|
+
constructor(initial: T);
|
|
57
|
+
set<K extends keyof T>(key: K, value: T[K]): this;
|
|
58
|
+
merge(overrides: Partial<T>): this;
|
|
59
|
+
build(): T;
|
|
60
|
+
buildMany(count: number): T[];
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,uBAAuB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AA0BD,qBAAa,QAAQ;IACjB;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAajD;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,KAAK,GAAE,MAAU,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE;IAMvE;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAcpD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAE,MAAU,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE;IAM1E;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAalD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,GAAE,MAAU,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE;IAMxE;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,GAAE,MAAsB,GAAG,MAAM;IAKpD;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAIhD;;OAEG;IACH,MAAM,CAAC,IAAI,IAAI,MAAM;IAQrB;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,GAAE,MAAU,GAAG,GAAG,GAAG,GAAG,EAAE;CAS/E;AAED;;GAEG;AACH,qBAAa,cAAc,CAAC,CAAC;IACzB,OAAO,CAAC,IAAI,CAAI;gBAEJ,OAAO,EAAE,CAAC;IAItB,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAKjD,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAKlC,KAAK,IAAI,CAAC;IAIV,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE;CAGhC"}
|
package/dist/fixtures.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Test Fixtures & Data Generators
|
|
4
|
+
* Utilities for creating realistic test data
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.FixtureBuilder = exports.Fixtures = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Simple seeded random number generator for reproducible tests
|
|
10
|
+
*/
|
|
11
|
+
class SeededRandom {
|
|
12
|
+
constructor(seed = Date.now()) {
|
|
13
|
+
this.seed = seed;
|
|
14
|
+
}
|
|
15
|
+
next() {
|
|
16
|
+
this.seed = (this.seed * 9301 + 49297) % 233280;
|
|
17
|
+
return this.seed / 233280;
|
|
18
|
+
}
|
|
19
|
+
nextInt(min, max) {
|
|
20
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
21
|
+
}
|
|
22
|
+
choice(arr) {
|
|
23
|
+
return arr[Math.floor(this.next() * arr.length)];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
class Fixtures {
|
|
27
|
+
/**
|
|
28
|
+
* Generate a user fixture
|
|
29
|
+
*/
|
|
30
|
+
static user(overrides) {
|
|
31
|
+
const id = Math.floor(Math.random() * 100000);
|
|
32
|
+
return {
|
|
33
|
+
id,
|
|
34
|
+
name: `User ${id}`,
|
|
35
|
+
email: `user${id}@example.com`,
|
|
36
|
+
role: 'user',
|
|
37
|
+
createdAt: new Date().toISOString(),
|
|
38
|
+
isActive: true,
|
|
39
|
+
...overrides
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate multiple user fixtures
|
|
44
|
+
*/
|
|
45
|
+
static users(count = 5, overrides) {
|
|
46
|
+
return Array.from({ length: count }, (_, i) => Fixtures.user({ id: i + 1, ...overrides }));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generate a product fixture
|
|
50
|
+
*/
|
|
51
|
+
static product(overrides) {
|
|
52
|
+
const id = Math.floor(Math.random() * 100000);
|
|
53
|
+
return {
|
|
54
|
+
id,
|
|
55
|
+
name: `Product ${id}`,
|
|
56
|
+
description: 'Test product description',
|
|
57
|
+
price: Math.floor(Math.random() * 10000) / 100,
|
|
58
|
+
stock: Math.floor(Math.random() * 1000),
|
|
59
|
+
sku: `SKU-${id}`,
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
...overrides
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generate multiple product fixtures
|
|
66
|
+
*/
|
|
67
|
+
static products(count = 5, overrides) {
|
|
68
|
+
return Array.from({ length: count }, (_, i) => Fixtures.product({ id: i + 1, ...overrides }));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Generate an order fixture
|
|
72
|
+
*/
|
|
73
|
+
static order(overrides) {
|
|
74
|
+
const id = Math.floor(Math.random() * 100000);
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
userId: Math.floor(Math.random() * 1000),
|
|
78
|
+
items: [{ productId: 1, quantity: 2, price: 29.99 }],
|
|
79
|
+
total: 59.98,
|
|
80
|
+
status: 'pending',
|
|
81
|
+
createdAt: new Date().toISOString(),
|
|
82
|
+
...overrides
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generate multiple order fixtures
|
|
87
|
+
*/
|
|
88
|
+
static orders(count = 5, overrides) {
|
|
89
|
+
return Array.from({ length: count }, (_, i) => Fixtures.order({ id: i + 1, ...overrides }));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Generate a random email
|
|
93
|
+
*/
|
|
94
|
+
static email(domain = 'example.com') {
|
|
95
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
96
|
+
return `test.${random}@${domain}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Generate a random string
|
|
100
|
+
*/
|
|
101
|
+
static randomString(length = 10) {
|
|
102
|
+
return Math.random().toString(36).substring(2, 2 + length);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Generate a random UUID
|
|
106
|
+
*/
|
|
107
|
+
static uuid() {
|
|
108
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
109
|
+
const r = (Math.random() * 16) | 0;
|
|
110
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
111
|
+
return v.toString(16);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a fixture with custom data
|
|
116
|
+
*/
|
|
117
|
+
static custom(baseData, count = 1) {
|
|
118
|
+
if (count === 1) {
|
|
119
|
+
return baseData;
|
|
120
|
+
}
|
|
121
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
122
|
+
...baseData,
|
|
123
|
+
id: i + 1
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.Fixtures = Fixtures;
|
|
128
|
+
/**
|
|
129
|
+
* Builder pattern for creating complex fixtures
|
|
130
|
+
*/
|
|
131
|
+
class FixtureBuilder {
|
|
132
|
+
constructor(initial) {
|
|
133
|
+
this.data = JSON.parse(JSON.stringify(initial));
|
|
134
|
+
}
|
|
135
|
+
set(key, value) {
|
|
136
|
+
this.data[key] = value;
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
merge(overrides) {
|
|
140
|
+
this.data = { ...this.data, ...overrides };
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
build() {
|
|
144
|
+
return JSON.parse(JSON.stringify(this.data));
|
|
145
|
+
}
|
|
146
|
+
buildMany(count) {
|
|
147
|
+
return Array.from({ length: count }, () => this.build());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.FixtureBuilder = FixtureBuilder;
|
|
151
|
+
//# sourceMappingURL=fixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Test Utilities
|
|
3
|
+
* Helpers for testing HTTP endpoints and mocking responses
|
|
4
|
+
*/
|
|
5
|
+
export interface HttpResponse {
|
|
6
|
+
status: number;
|
|
7
|
+
body: any;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export declare class HttpTestBuilder {
|
|
11
|
+
/**
|
|
12
|
+
* Create a successful response (200)
|
|
13
|
+
*/
|
|
14
|
+
static ok(body?: any): HttpResponse;
|
|
15
|
+
/**
|
|
16
|
+
* created response (201)
|
|
17
|
+
*/
|
|
18
|
+
static created(body: any): HttpResponse;
|
|
19
|
+
/**
|
|
20
|
+
*bad request response (400)
|
|
21
|
+
*/
|
|
22
|
+
static badRequest(message?: string): HttpResponse;
|
|
23
|
+
/**
|
|
24
|
+
* unauthorized response (401)
|
|
25
|
+
*/
|
|
26
|
+
static unauthorized(message?: string): HttpResponse;
|
|
27
|
+
/**
|
|
28
|
+
* forbidden response (403)
|
|
29
|
+
*/
|
|
30
|
+
static forbidden(message?: string): HttpResponse;
|
|
31
|
+
/**
|
|
32
|
+
* not found response (404)
|
|
33
|
+
*/
|
|
34
|
+
static notFound(message?: string): HttpResponse;
|
|
35
|
+
/**
|
|
36
|
+
* server error response (500)
|
|
37
|
+
*/
|
|
38
|
+
static serverError(message?: string): HttpResponse;
|
|
39
|
+
/**
|
|
40
|
+
* custom response
|
|
41
|
+
*/
|
|
42
|
+
static custom(status: number, body?: any, headers?: Record<string, string>): HttpResponse;
|
|
43
|
+
}
|
|
44
|
+
export declare class HttpResponseAssertion {
|
|
45
|
+
/**
|
|
46
|
+
* response status code
|
|
47
|
+
*/
|
|
48
|
+
static assertStatus(response: HttpResponse, expectedStatus: number): void;
|
|
49
|
+
/**
|
|
50
|
+
* response body contains key
|
|
51
|
+
*/
|
|
52
|
+
static assertBodyHasKey(response: HttpResponse, key: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* response body matches object
|
|
55
|
+
*/
|
|
56
|
+
static assertBodyMatches(response: HttpResponse, expected: Record<string, any>): void;
|
|
57
|
+
/**
|
|
58
|
+
* response header exists
|
|
59
|
+
*/
|
|
60
|
+
static assertHeaderExists(response: HttpResponse, headerName: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* response is successful (2xx)
|
|
63
|
+
*/
|
|
64
|
+
static assertSuccess(response: HttpResponse): void;
|
|
65
|
+
/**
|
|
66
|
+
* response is error (4xx or 5xx)
|
|
67
|
+
*/
|
|
68
|
+
static assertError(response: HttpResponse): void;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=http-test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-test.d.ts","sourceRoot":"","sources":["../src/http-test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,GAAG,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,qBAAa,eAAe;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,GAAE,GAAQ,GAAG,YAAY;IAQvC;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,YAAY;IAQvC;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,OAAO,GAAE,MAAsB,GAAG,YAAY;IAQhE;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,GAAE,MAAuB,GAAG,YAAY;IAQnE;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,MAAoB,GAAG,YAAY;IAQ7D;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAE,MAAoB,GAAG,YAAY;IAQ5D;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,GAAE,MAAgC,GAAG,YAAY;IAQ3E;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,GAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY;CAGhG;AAED,qBAAa,qBAAqB;IAC9B;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAQzE;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAQlE;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAYrF;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAS3E;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAQlD;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;CAOnD"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP Test Utilities
|
|
4
|
+
* Helpers for testing HTTP endpoints and mocking responses
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.HttpResponseAssertion = exports.HttpTestBuilder = void 0;
|
|
8
|
+
class HttpTestBuilder {
|
|
9
|
+
/**
|
|
10
|
+
* Create a successful response (200)
|
|
11
|
+
*/
|
|
12
|
+
static ok(body = {}) {
|
|
13
|
+
return {
|
|
14
|
+
status: 200,
|
|
15
|
+
body,
|
|
16
|
+
headers: { 'Content-Type': 'application/json' }
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* created response (201)
|
|
21
|
+
*/
|
|
22
|
+
static created(body) {
|
|
23
|
+
return {
|
|
24
|
+
status: 201,
|
|
25
|
+
body,
|
|
26
|
+
headers: { 'Content-Type': 'application/json' }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
*bad request response (400)
|
|
31
|
+
*/
|
|
32
|
+
static badRequest(message = 'Bad Request') {
|
|
33
|
+
return {
|
|
34
|
+
status: 400,
|
|
35
|
+
body: { error: message },
|
|
36
|
+
headers: { 'Content-Type': 'application/json' }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* unauthorized response (401)
|
|
41
|
+
*/
|
|
42
|
+
static unauthorized(message = 'Unauthorized') {
|
|
43
|
+
return {
|
|
44
|
+
status: 401,
|
|
45
|
+
body: { error: message },
|
|
46
|
+
headers: { 'Content-Type': 'application/json' }
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* forbidden response (403)
|
|
51
|
+
*/
|
|
52
|
+
static forbidden(message = 'Forbidden') {
|
|
53
|
+
return {
|
|
54
|
+
status: 403,
|
|
55
|
+
body: { error: message },
|
|
56
|
+
headers: { 'Content-Type': 'application/json' }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* not found response (404)
|
|
61
|
+
*/
|
|
62
|
+
static notFound(message = 'Not Found') {
|
|
63
|
+
return {
|
|
64
|
+
status: 404,
|
|
65
|
+
body: { error: message },
|
|
66
|
+
headers: { 'Content-Type': 'application/json' }
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* server error response (500)
|
|
71
|
+
*/
|
|
72
|
+
static serverError(message = 'Internal Server Error') {
|
|
73
|
+
return {
|
|
74
|
+
status: 500,
|
|
75
|
+
body: { error: message },
|
|
76
|
+
headers: { 'Content-Type': 'application/json' }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* custom response
|
|
81
|
+
*/
|
|
82
|
+
static custom(status, body = {}, headers) {
|
|
83
|
+
return { status, body, headers };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.HttpTestBuilder = HttpTestBuilder;
|
|
87
|
+
class HttpResponseAssertion {
|
|
88
|
+
/**
|
|
89
|
+
* response status code
|
|
90
|
+
*/
|
|
91
|
+
static assertStatus(response, expectedStatus) {
|
|
92
|
+
if (response.status !== expectedStatus) {
|
|
93
|
+
throw new Error(`Expected status ${expectedStatus}, but got ${response.status}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* response body contains key
|
|
98
|
+
*/
|
|
99
|
+
static assertBodyHasKey(response, key) {
|
|
100
|
+
if (!(key in response.body)) {
|
|
101
|
+
throw new Error(`Expected response body to contain key "${key}", but it doesn't`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* response body matches object
|
|
106
|
+
*/
|
|
107
|
+
static assertBodyMatches(response, expected) {
|
|
108
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
109
|
+
if (response.body[key] !== value) {
|
|
110
|
+
throw new Error(`Expected body.${key} to be ${JSON.stringify(value)}, but got ${JSON.stringify(response.body[key])}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* response header exists
|
|
116
|
+
*/
|
|
117
|
+
static assertHeaderExists(response, headerName) {
|
|
118
|
+
const headers = response.headers || {};
|
|
119
|
+
if (!(headerName in headers)) {
|
|
120
|
+
throw new Error(`Expected header "${headerName}" to exist, but it doesn't`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* response is successful (2xx)
|
|
125
|
+
*/
|
|
126
|
+
static assertSuccess(response) {
|
|
127
|
+
if (response.status < 200 || response.status >= 300) {
|
|
128
|
+
throw new Error(`Expected successful response (2xx), but got ${response.status}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* response is error (4xx or 5xx)
|
|
133
|
+
*/
|
|
134
|
+
static assertError(response) {
|
|
135
|
+
if (response.status < 400) {
|
|
136
|
+
throw new Error(`Expected error response (4xx/5xx), but got ${response.status}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.HttpResponseAssertion = HttpResponseAssertion;
|
|
141
|
+
//# sourceMappingURL=http-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-test.js","sourceRoot":"","sources":["../src/http-test.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAQH,MAAa,eAAe;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,OAAY,EAAE;QACpB,OAAO;YACH,MAAM,EAAE,GAAG;YACX,IAAI;YACJ,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAClD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,IAAS;QACpB,OAAO;YACH,MAAM,EAAE,GAAG;YACX,IAAI;YACJ,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAClD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,UAAkB,aAAa;QAC7C,OAAO;YACH,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;YACxB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAClD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,UAAkB,cAAc;QAChD,OAAO;YACH,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;YACxB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAClD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,UAAkB,WAAW;QAC1C,OAAO;YACH,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;YACxB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAClD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,UAAkB,WAAW;QACzC,OAAO;YACH,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;YACxB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAClD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,UAAkB,uBAAuB;QACxD,OAAO;YACH,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;YACxB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAClD,CAAC;IACN,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAc,EAAE,OAAY,EAAE,EAAE,OAAgC;QAC1E,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACrC,CAAC;CACJ;AApFD,0CAoFC;AAED,MAAa,qBAAqB;IAC9B;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,QAAsB,EAAE,cAAsB;QAC9D,IAAI,QAAQ,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACX,mBAAmB,cAAc,aAAa,QAAQ,CAAC,MAAM,EAAE,CAClE,CAAC;QACN,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAsB,EAAE,GAAW;QACvD,IAAI,CAAC,CAAC,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CACX,0CAA0C,GAAG,mBAAmB,CACnE,CAAC;QACN,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAsB,EAAE,QAA6B;QAC1E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClD,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CACX,iBAAiB,GAAG,UAAU,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,SAAS,CAC1E,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CACrB,EAAE,CACN,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,QAAsB,EAAE,UAAkB;QAChE,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACX,oBAAoB,UAAU,4BAA4B,CAC7D,CAAC;QACN,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,QAAsB;QACvC,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CACX,+CAA+C,QAAQ,CAAC,MAAM,EAAE,CACnE,CAAC;QACN,CAAC;IACL,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,QAAsB;QACrC,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACX,8CAA8C,QAAQ,CAAC,MAAM,EAAE,CAClE,CAAC;QACN,CAAC;IACL,CAAC;CACJ;AAvED,sDAuEC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @backend-master/test-utils
|
|
3
|
+
* Comprehensive testing utilities for Node.js backend projects
|
|
4
|
+
*/
|
|
5
|
+
import { MockDatabase } from './mock-db';
|
|
6
|
+
import { HttpTestBuilder, HttpResponseAssertion } from './http-test';
|
|
7
|
+
import { Fixtures, FixtureBuilder } from './fixtures';
|
|
8
|
+
import { Spy, MockObject, Defer } from './spy';
|
|
9
|
+
export { MockDatabase, MockDBConfig } from './mock-db';
|
|
10
|
+
export { HttpTestBuilder, HttpResponseAssertion, HttpResponse } from './http-test';
|
|
11
|
+
export { Fixtures, FixtureBuilder } from './fixtures';
|
|
12
|
+
export { Spy, MockObject, Defer, CallInfo } from './spy';
|
|
13
|
+
export declare const testUtils: {
|
|
14
|
+
db: {
|
|
15
|
+
MockDatabase: typeof MockDatabase;
|
|
16
|
+
};
|
|
17
|
+
http: {
|
|
18
|
+
HttpTestBuilder: typeof HttpTestBuilder;
|
|
19
|
+
HttpResponseAssertion: typeof HttpResponseAssertion;
|
|
20
|
+
};
|
|
21
|
+
fixtures: {
|
|
22
|
+
Fixtures: typeof Fixtures;
|
|
23
|
+
FixtureBuilder: typeof FixtureBuilder;
|
|
24
|
+
};
|
|
25
|
+
mocks: {
|
|
26
|
+
Spy: typeof Spy;
|
|
27
|
+
MockObject: typeof MockObject;
|
|
28
|
+
Defer: typeof Defer;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGzD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;CAiBrB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @backend-master/test-utils
|
|
4
|
+
* Comprehensive testing utilities for Node.js backend projects
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.testUtils = exports.Defer = exports.MockObject = exports.Spy = exports.FixtureBuilder = exports.Fixtures = exports.HttpResponseAssertion = exports.HttpTestBuilder = exports.MockDatabase = void 0;
|
|
8
|
+
const mock_db_1 = require("./mock-db");
|
|
9
|
+
const http_test_1 = require("./http-test");
|
|
10
|
+
const fixtures_1 = require("./fixtures");
|
|
11
|
+
const spy_1 = require("./spy");
|
|
12
|
+
var mock_db_2 = require("./mock-db");
|
|
13
|
+
Object.defineProperty(exports, "MockDatabase", { enumerable: true, get: function () { return mock_db_2.MockDatabase; } });
|
|
14
|
+
var http_test_2 = require("./http-test");
|
|
15
|
+
Object.defineProperty(exports, "HttpTestBuilder", { enumerable: true, get: function () { return http_test_2.HttpTestBuilder; } });
|
|
16
|
+
Object.defineProperty(exports, "HttpResponseAssertion", { enumerable: true, get: function () { return http_test_2.HttpResponseAssertion; } });
|
|
17
|
+
var fixtures_2 = require("./fixtures");
|
|
18
|
+
Object.defineProperty(exports, "Fixtures", { enumerable: true, get: function () { return fixtures_2.Fixtures; } });
|
|
19
|
+
Object.defineProperty(exports, "FixtureBuilder", { enumerable: true, get: function () { return fixtures_2.FixtureBuilder; } });
|
|
20
|
+
var spy_2 = require("./spy");
|
|
21
|
+
Object.defineProperty(exports, "Spy", { enumerable: true, get: function () { return spy_2.Spy; } });
|
|
22
|
+
Object.defineProperty(exports, "MockObject", { enumerable: true, get: function () { return spy_2.MockObject; } });
|
|
23
|
+
Object.defineProperty(exports, "Defer", { enumerable: true, get: function () { return spy_2.Defer; } });
|
|
24
|
+
// Convenience exports
|
|
25
|
+
exports.testUtils = {
|
|
26
|
+
db: {
|
|
27
|
+
MockDatabase: mock_db_1.MockDatabase
|
|
28
|
+
},
|
|
29
|
+
http: {
|
|
30
|
+
HttpTestBuilder: http_test_1.HttpTestBuilder,
|
|
31
|
+
HttpResponseAssertion: http_test_1.HttpResponseAssertion
|
|
32
|
+
},
|
|
33
|
+
fixtures: {
|
|
34
|
+
Fixtures: fixtures_1.Fixtures,
|
|
35
|
+
FixtureBuilder: fixtures_1.FixtureBuilder
|
|
36
|
+
},
|
|
37
|
+
mocks: {
|
|
38
|
+
Spy: spy_1.Spy,
|
|
39
|
+
MockObject: spy_1.MockObject,
|
|
40
|
+
Defer: spy_1.Defer
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,uCAAyC;AACzC,2CAAqE;AACrE,yCAAsD;AACtD,+BAA+C;AAE/C,qCAAuD;AAA9C,uGAAA,YAAY,OAAA;AACrB,yCAAmF;AAA1E,4GAAA,eAAe,OAAA;AAAE,kHAAA,qBAAqB,OAAA;AAC/C,uCAAsD;AAA7C,oGAAA,QAAQ,OAAA;AAAE,0GAAA,cAAc,OAAA;AACjC,6BAAyD;AAAhD,0FAAA,GAAG,OAAA;AAAE,iGAAA,UAAU,OAAA;AAAE,4FAAA,KAAK,OAAA;AAE/B,sBAAsB;AACT,QAAA,SAAS,GAAG;IACrB,EAAE,EAAE;QACA,YAAY,EAAZ,sBAAY;KACf;IACD,IAAI,EAAE;QACF,eAAe,EAAf,2BAAe;QACf,qBAAqB,EAArB,iCAAqB;KACxB;IACD,QAAQ,EAAE;QACN,QAAQ,EAAR,mBAAQ;QACR,cAAc,EAAd,yBAAc;KACjB;IACD,KAAK,EAAE;QACH,GAAG,EAAH,SAAG;QACH,UAAU,EAAV,gBAAU;QACV,KAAK,EAAL,WAAK;KACR;CACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Database Helper
|
|
3
|
+
* Provides in-memory database simulation for testing without external dependencies
|
|
4
|
+
*/
|
|
5
|
+
export interface MockDBConfig {
|
|
6
|
+
tables?: Record<string, any[]>;
|
|
7
|
+
autoIncrement?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class MockDatabase {
|
|
10
|
+
private tables;
|
|
11
|
+
private autoIncrement;
|
|
12
|
+
private idCounters;
|
|
13
|
+
constructor(config?: MockDBConfig);
|
|
14
|
+
/**
|
|
15
|
+
* Create a new table
|
|
16
|
+
*/
|
|
17
|
+
createTable(name: string, schema?: Record<string, string>): void;
|
|
18
|
+
/**
|
|
19
|
+
* Insert a record into a table
|
|
20
|
+
*/
|
|
21
|
+
insert(table: string, record: any): any;
|
|
22
|
+
/**
|
|
23
|
+
* Find records matching a query
|
|
24
|
+
*/
|
|
25
|
+
find(table: string, query?: Record<string, any>): any[];
|
|
26
|
+
/**
|
|
27
|
+
* Find a single record
|
|
28
|
+
*/
|
|
29
|
+
findOne(table: string, query: Record<string, any>): any | null;
|
|
30
|
+
/**
|
|
31
|
+
* Update records matching a query
|
|
32
|
+
*/
|
|
33
|
+
update(table: string, query: Record<string, any>, updates: Record<string, any>): number;
|
|
34
|
+
/**
|
|
35
|
+
* Delete records matching a query
|
|
36
|
+
*/
|
|
37
|
+
delete(table: string, query: Record<string, any>): number;
|
|
38
|
+
/**
|
|
39
|
+
* Get all records from a table
|
|
40
|
+
*/
|
|
41
|
+
getAll(table: string): any[];
|
|
42
|
+
/**
|
|
43
|
+
* Clear a table
|
|
44
|
+
*/
|
|
45
|
+
clearTable(table: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Clear all tables
|
|
48
|
+
*/
|
|
49
|
+
clearAll(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Get table count
|
|
52
|
+
*/
|
|
53
|
+
count(table: string): number;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=mock-db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-db.d.ts","sourceRoot":"","sources":["../src/mock-db.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,UAAU,CAAkC;gBAExC,MAAM,CAAC,EAAE,YAAY;IASjC;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAQhE;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,GAAG;IAgBvC;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAAG,GAAG,EAAE;IAW3D;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,IAAI;IAK9D;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;IAqBvF;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;IAoBzD;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAO5B;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ/B;;OAEG;IACH,QAAQ,IAAI,IAAI;IAKhB;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAM/B"}
|
package/dist/mock-db.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mock Database Helper
|
|
4
|
+
* Provides in-memory database simulation for testing without external dependencies
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.MockDatabase = void 0;
|
|
8
|
+
class MockDatabase {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.tables = new Map();
|
|
11
|
+
this.idCounters = new Map();
|
|
12
|
+
this.autoIncrement = config?.autoIncrement ?? true;
|
|
13
|
+
if (config?.tables) {
|
|
14
|
+
Object.entries(config.tables).forEach(([table, data]) => {
|
|
15
|
+
this.tables.set(table, JSON.parse(JSON.stringify(data)));
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a new table
|
|
21
|
+
*/
|
|
22
|
+
createTable(name, schema) {
|
|
23
|
+
if (this.tables.has(name)) {
|
|
24
|
+
throw new Error(`Table "${name}" already exists`);
|
|
25
|
+
}
|
|
26
|
+
this.tables.set(name, []);
|
|
27
|
+
this.idCounters.set(name, 0);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Insert a record into a table
|
|
31
|
+
*/
|
|
32
|
+
insert(table, record) {
|
|
33
|
+
if (!this.tables.has(table)) {
|
|
34
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
35
|
+
}
|
|
36
|
+
const newRecord = { ...record };
|
|
37
|
+
if (this.autoIncrement && !newRecord.id) {
|
|
38
|
+
const count = this.idCounters.get(table) || 0;
|
|
39
|
+
newRecord.id = count + 1;
|
|
40
|
+
this.idCounters.set(table, count + 1);
|
|
41
|
+
}
|
|
42
|
+
this.tables.get(table).push(newRecord);
|
|
43
|
+
return newRecord;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Find records matching a query
|
|
47
|
+
*/
|
|
48
|
+
find(table, query = {}) {
|
|
49
|
+
if (!this.tables.has(table)) {
|
|
50
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
51
|
+
}
|
|
52
|
+
const records = this.tables.get(table);
|
|
53
|
+
return records.filter(record => Object.entries(query).every(([key, value]) => record[key] === value));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Find a single record
|
|
57
|
+
*/
|
|
58
|
+
findOne(table, query) {
|
|
59
|
+
const results = this.find(table, query);
|
|
60
|
+
return results.length > 0 ? results[0] : null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Update records matching a query
|
|
64
|
+
*/
|
|
65
|
+
update(table, query, updates) {
|
|
66
|
+
if (!this.tables.has(table)) {
|
|
67
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
68
|
+
}
|
|
69
|
+
const records = this.tables.get(table);
|
|
70
|
+
let updated = 0;
|
|
71
|
+
for (let i = 0; i < records.length; i++) {
|
|
72
|
+
const matches = Object.entries(query).every(([key, value]) => records[i][key] === value);
|
|
73
|
+
if (matches) {
|
|
74
|
+
records[i] = { ...records[i], ...updates };
|
|
75
|
+
updated++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return updated;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Delete records matching a query
|
|
82
|
+
*/
|
|
83
|
+
delete(table, query) {
|
|
84
|
+
if (!this.tables.has(table)) {
|
|
85
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
86
|
+
}
|
|
87
|
+
const records = this.tables.get(table);
|
|
88
|
+
const originalLength = records.length;
|
|
89
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
90
|
+
const matches = Object.entries(query).every(([key, value]) => records[i][key] === value);
|
|
91
|
+
if (matches) {
|
|
92
|
+
records.splice(i, 1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return originalLength - records.length;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get all records from a table
|
|
99
|
+
*/
|
|
100
|
+
getAll(table) {
|
|
101
|
+
if (!this.tables.has(table)) {
|
|
102
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
103
|
+
}
|
|
104
|
+
return JSON.parse(JSON.stringify(this.tables.get(table)));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Clear a table
|
|
108
|
+
*/
|
|
109
|
+
clearTable(table) {
|
|
110
|
+
if (!this.tables.has(table)) {
|
|
111
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
112
|
+
}
|
|
113
|
+
this.tables.set(table, []);
|
|
114
|
+
this.idCounters.set(table, 0);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Clear all tables
|
|
118
|
+
*/
|
|
119
|
+
clearAll() {
|
|
120
|
+
this.tables.clear();
|
|
121
|
+
this.idCounters.clear();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get table count
|
|
125
|
+
*/
|
|
126
|
+
count(table) {
|
|
127
|
+
if (!this.tables.has(table)) {
|
|
128
|
+
throw new Error(`Table "${table}" does not exist`);
|
|
129
|
+
}
|
|
130
|
+
return this.tables.get(table).length;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.MockDatabase = MockDatabase;
|
|
134
|
+
//# sourceMappingURL=mock-db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-db.js","sourceRoot":"","sources":["../src/mock-db.ts"],"names":[],"mappings":"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"}
|
package/dist/spy.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spy & Mock Utilities
|
|
3
|
+
* Helpers for tracking function calls and creating mocks
|
|
4
|
+
*/
|
|
5
|
+
export interface CallInfo {
|
|
6
|
+
args: any[];
|
|
7
|
+
returnValue?: any;
|
|
8
|
+
error?: Error;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class Spy {
|
|
12
|
+
private calls;
|
|
13
|
+
private _returnValue;
|
|
14
|
+
private shouldThrow;
|
|
15
|
+
/**
|
|
16
|
+
* Create a spy function
|
|
17
|
+
*/
|
|
18
|
+
static create<T extends (...args: any[]) => any>(implementation?: T): T & Spy;
|
|
19
|
+
/**
|
|
20
|
+
* Set return value
|
|
21
|
+
*/
|
|
22
|
+
returnValue(value: any): this;
|
|
23
|
+
/**
|
|
24
|
+
* Set up to throw error
|
|
25
|
+
*/
|
|
26
|
+
throwError(error: Error | string): this;
|
|
27
|
+
/**
|
|
28
|
+
* Implement with custom function
|
|
29
|
+
*/
|
|
30
|
+
implementWith<T extends (...args: any[]) => any>(fn: T): T & Spy;
|
|
31
|
+
/**
|
|
32
|
+
* Record a call
|
|
33
|
+
*/
|
|
34
|
+
private record;
|
|
35
|
+
/**
|
|
36
|
+
* Get all calls
|
|
37
|
+
*/
|
|
38
|
+
getCalls(): CallInfo[];
|
|
39
|
+
/**
|
|
40
|
+
* Get call count
|
|
41
|
+
*/
|
|
42
|
+
getCallCount(): number;
|
|
43
|
+
/**
|
|
44
|
+
* Get arguments of nth call
|
|
45
|
+
*/
|
|
46
|
+
getArguments(callIndex?: number): any[];
|
|
47
|
+
/**
|
|
48
|
+
* Get first argument of first call
|
|
49
|
+
*/
|
|
50
|
+
getFirstArg(): any;
|
|
51
|
+
/**
|
|
52
|
+
* Check if called
|
|
53
|
+
*/
|
|
54
|
+
wasCalled(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Check if called with specific arguments
|
|
57
|
+
*/
|
|
58
|
+
wasCalledWith(...args: any[]): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Reset spy
|
|
61
|
+
*/
|
|
62
|
+
reset(): void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Maps an object type so function-valued properties become T[K] & Spy
|
|
66
|
+
*/
|
|
67
|
+
type SpyWrapped<T> = {
|
|
68
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] & Spy : T[K];
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Maps an object type so all properties become T[K] & Spy
|
|
72
|
+
*/
|
|
73
|
+
type AllSpies<T> = {
|
|
74
|
+
[K in keyof T]: T[K] & Spy;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Simple object mocking
|
|
78
|
+
*/
|
|
79
|
+
export declare class MockObject {
|
|
80
|
+
static create<T extends Record<string, any>>(shape: Partial<T>): SpyWrapped<T>;
|
|
81
|
+
/**
|
|
82
|
+
* Create a mock with all methods as spies
|
|
83
|
+
*/
|
|
84
|
+
static createWithSpies<T extends Record<string, any>>(keys: (keyof T)[]): AllSpies<T>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Defer helper for testing async code
|
|
88
|
+
*/
|
|
89
|
+
export declare class Defer<T> {
|
|
90
|
+
promise: Promise<T>;
|
|
91
|
+
resolve: (value: T) => void;
|
|
92
|
+
reject: (reason?: any) => void;
|
|
93
|
+
constructor();
|
|
94
|
+
}
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=spy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spy.d.ts","sourceRoot":"","sources":["../src/spy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,GAAG;IACZ,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,WAAW,CAAsB;IAEzC;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAC3C,cAAc,CAAC,EAAE,CAAC,GACnB,CAAC,GAAG,GAAG;IAwBV;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI;IAKvC;;OAEG;IACH,aAAa,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG;IAmChE;;OAEG;IACH,OAAO,CAAC,MAAM;IAiBd;;OAEG;IACH,QAAQ,IAAI,QAAQ,EAAE;IAItB;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,YAAY,CAAC,SAAS,GAAE,MAAU,GAAG,GAAG,EAAE;IAO1C;;OAEG;IACH,WAAW,IAAI,GAAG;IAIlB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,aAAa,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO;IAMtC;;OAEG;IACH,KAAK,IAAI,IAAI;CAKhB;AAED;;GAEG;AACH,KAAK,UAAU,CAAC,CAAC,IAAI;KAChB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;CAC3E,CAAC;AAEF;;GAEG;AACH,KAAK,QAAQ,CAAC,CAAC,IAAI;KACd,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG;CAC7B,CAAC;AAEF;;GAEG;AACH,qBAAa,UAAU;IACnB,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAc9E;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAChD,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,GAClB,QAAQ,CAAC,CAAC,CAAC;CASjB;AAGD;;GAEG;AACH,qBAAa,KAAK,CAAC,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAG,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,MAAM,EAAG,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;;CAQnC"}
|
package/dist/spy.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Spy & Mock Utilities
|
|
4
|
+
* Helpers for tracking function calls and creating mocks
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Defer = exports.MockObject = exports.Spy = void 0;
|
|
8
|
+
class Spy {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.calls = [];
|
|
11
|
+
this.shouldThrow = null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a spy function
|
|
15
|
+
*/
|
|
16
|
+
static create(implementation) {
|
|
17
|
+
const spy = new Spy();
|
|
18
|
+
if (implementation) {
|
|
19
|
+
return spy.implementWith(implementation);
|
|
20
|
+
}
|
|
21
|
+
const fn = function (...args) {
|
|
22
|
+
return spy.record(args);
|
|
23
|
+
};
|
|
24
|
+
Object.setPrototypeOf(fn, Spy.prototype);
|
|
25
|
+
// Proxy property access to the spy instance so state stays in sync
|
|
26
|
+
for (const prop of ['calls', '_returnValue', 'shouldThrow']) {
|
|
27
|
+
Object.defineProperty(fn, prop, {
|
|
28
|
+
get: () => spy[prop],
|
|
29
|
+
set: (v) => { spy[prop] = v; },
|
|
30
|
+
configurable: true
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return fn;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Set return value
|
|
37
|
+
*/
|
|
38
|
+
returnValue(value) {
|
|
39
|
+
this._returnValue = value;
|
|
40
|
+
this.shouldThrow = null;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Set up to throw error
|
|
45
|
+
*/
|
|
46
|
+
throwError(error) {
|
|
47
|
+
this.shouldThrow = typeof error === 'string' ? new Error(error) : error;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Implement with custom function
|
|
52
|
+
*/
|
|
53
|
+
implementWith(fn) {
|
|
54
|
+
const spy = this;
|
|
55
|
+
const originalFn = fn;
|
|
56
|
+
const wrappedFn = function (...args) {
|
|
57
|
+
try {
|
|
58
|
+
const result = originalFn(...args);
|
|
59
|
+
spy.calls.push({
|
|
60
|
+
args,
|
|
61
|
+
returnValue: result,
|
|
62
|
+
timestamp: Date.now()
|
|
63
|
+
});
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
spy.calls.push({
|
|
68
|
+
args,
|
|
69
|
+
error: error,
|
|
70
|
+
timestamp: Date.now()
|
|
71
|
+
});
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
Object.setPrototypeOf(wrappedFn, Spy.prototype);
|
|
76
|
+
// Proxy property access to the spy instance so state stays in sync
|
|
77
|
+
for (const prop of ['calls', '_returnValue', 'shouldThrow']) {
|
|
78
|
+
Object.defineProperty(wrappedFn, prop, {
|
|
79
|
+
get: () => spy[prop],
|
|
80
|
+
set: (v) => { spy[prop] = v; },
|
|
81
|
+
configurable: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return wrappedFn;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Record a call
|
|
88
|
+
*/
|
|
89
|
+
record(args) {
|
|
90
|
+
const callInfo = {
|
|
91
|
+
args,
|
|
92
|
+
timestamp: Date.now()
|
|
93
|
+
};
|
|
94
|
+
if (this.shouldThrow) {
|
|
95
|
+
callInfo.error = this.shouldThrow;
|
|
96
|
+
this.calls.push(callInfo);
|
|
97
|
+
throw this.shouldThrow;
|
|
98
|
+
}
|
|
99
|
+
callInfo.returnValue = this._returnValue;
|
|
100
|
+
this.calls.push(callInfo);
|
|
101
|
+
return this._returnValue;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get all calls
|
|
105
|
+
*/
|
|
106
|
+
getCalls() {
|
|
107
|
+
return [...this.calls];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get call count
|
|
111
|
+
*/
|
|
112
|
+
getCallCount() {
|
|
113
|
+
return this.calls.length;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get arguments of nth call
|
|
117
|
+
*/
|
|
118
|
+
getArguments(callIndex = 0) {
|
|
119
|
+
if (callIndex >= this.calls.length) {
|
|
120
|
+
throw new Error(`Call at index ${callIndex} does not exist`);
|
|
121
|
+
}
|
|
122
|
+
return this.calls[callIndex].args;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get first argument of first call
|
|
126
|
+
*/
|
|
127
|
+
getFirstArg() {
|
|
128
|
+
return this.getArguments(0)[0];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if called
|
|
132
|
+
*/
|
|
133
|
+
wasCalled() {
|
|
134
|
+
return this.calls.length > 0;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if called with specific arguments
|
|
138
|
+
*/
|
|
139
|
+
wasCalledWith(...args) {
|
|
140
|
+
return this.calls.some(call => JSON.stringify(call.args) === JSON.stringify(args));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Reset spy
|
|
144
|
+
*/
|
|
145
|
+
reset() {
|
|
146
|
+
this.calls.length = 0;
|
|
147
|
+
this._returnValue = undefined;
|
|
148
|
+
this.shouldThrow = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.Spy = Spy;
|
|
152
|
+
/**
|
|
153
|
+
* Simple object mocking
|
|
154
|
+
*/
|
|
155
|
+
class MockObject {
|
|
156
|
+
static create(shape) {
|
|
157
|
+
const mock = {};
|
|
158
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
159
|
+
if (typeof value === 'function') {
|
|
160
|
+
mock[key] = Spy.create(value);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
mock[key] = value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return mock;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Create a mock with all methods as spies
|
|
170
|
+
*/
|
|
171
|
+
static createWithSpies(keys) {
|
|
172
|
+
const mock = {};
|
|
173
|
+
for (const key of keys) {
|
|
174
|
+
mock[key] = Spy.create();
|
|
175
|
+
}
|
|
176
|
+
return mock;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.MockObject = MockObject;
|
|
180
|
+
/**
|
|
181
|
+
* Defer helper for testing async code
|
|
182
|
+
*/
|
|
183
|
+
class Defer {
|
|
184
|
+
constructor() {
|
|
185
|
+
this.promise = new Promise((resolve, reject) => {
|
|
186
|
+
this.resolve = resolve;
|
|
187
|
+
this.reject = reject;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
exports.Defer = Defer;
|
|
192
|
+
//# sourceMappingURL=spy.js.map
|
package/dist/spy.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spy.js","sourceRoot":"","sources":["../src/spy.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backend-master/test-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Comprehensive testing utilities for Node.js backend projects",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"test:watch": "jest --watch",
|
|
15
|
+
"test:coverage": "jest --coverage",
|
|
16
|
+
"lint": "eslint src/**/*.ts",
|
|
17
|
+
"prepare": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"testing",
|
|
21
|
+
"node.js",
|
|
22
|
+
"backend",
|
|
23
|
+
"utilities",
|
|
24
|
+
"mock",
|
|
25
|
+
"fixture",
|
|
26
|
+
"assertion"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": ""
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/jest": "^29.5.0",
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
41
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
42
|
+
"eslint": "^8.0.0",
|
|
43
|
+
"jest": "^29.5.0",
|
|
44
|
+
"ts-jest": "^29.1.0",
|
|
45
|
+
"typescript": "^5.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"jest": ">=25.0.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesOptional": true
|
|
51
|
+
}
|