@digitaldefiance/express-suite-test-utils 1.0.9 → 1.0.11
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 +249 -0
- package/package.json +1 -1
- package/src/lib/mongoose-memory.d.ts +6 -2
- package/src/lib/mongoose-memory.d.ts.map +1 -1
- package/src/lib/mongoose-memory.js +17 -8
package/README.md
CHANGED
|
@@ -97,12 +97,261 @@ it('should log message', async () => {
|
|
|
97
97
|
});
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
### Direct Log Mocks
|
|
101
|
+
|
|
102
|
+
Mock `fs.writeSync` for testing direct console output:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { withDirectLogMocks, directLogContains, getDirectLogMessages } from '@digitaldefiance/express-suite-test-utils';
|
|
106
|
+
import * as fs from 'fs';
|
|
107
|
+
|
|
108
|
+
// Important: Mock fs module at module level before importing
|
|
109
|
+
jest.mock('fs', () => ({
|
|
110
|
+
...jest.requireActual('fs'),
|
|
111
|
+
writeSync: jest.fn(),
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
it('should capture direct writes to stdout', async () => {
|
|
115
|
+
await withDirectLogMocks({ mute: true }, async (spies) => {
|
|
116
|
+
const buffer = Buffer.from('hello world\n', 'utf8');
|
|
117
|
+
fs.writeSync(1, buffer); // stdout
|
|
118
|
+
|
|
119
|
+
expect(directLogContains(spies.writeSync, 1, 'hello', 'world')).toBe(true);
|
|
120
|
+
expect(getDirectLogMessages(spies.writeSync, 1)).toEqual(['hello world\n']);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Mongoose Memory Database
|
|
126
|
+
|
|
127
|
+
In-memory MongoDB testing utilities using mongodb-memory-server:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { connectMemoryDB, disconnectMemoryDB, clearMemoryDB } from '@digitaldefiance/express-suite-test-utils';
|
|
131
|
+
import { User } from './models/user';
|
|
132
|
+
|
|
133
|
+
describe('User model', () => {
|
|
134
|
+
beforeAll(async () => {
|
|
135
|
+
await connectMemoryDB();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
afterAll(async () => {
|
|
139
|
+
await disconnectMemoryDB();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterEach(async () => {
|
|
143
|
+
await clearMemoryDB();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should validate user schema', async () => {
|
|
147
|
+
const user = new User({ username: 'test', email: 'test@example.com' });
|
|
148
|
+
await user.validate(); // Real Mongoose validation!
|
|
149
|
+
|
|
150
|
+
await expect(async () => {
|
|
151
|
+
const invalid = new User({ username: 'ab' }); // too short
|
|
152
|
+
await invalid.validate();
|
|
153
|
+
}).rejects.toThrow();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Note:** Requires `mongoose` as a peer dependency and `mongodb-memory-server` as a dependency (already included).
|
|
159
|
+
|
|
160
|
+
## Testing Approach
|
|
161
|
+
|
|
162
|
+
This package provides comprehensive testing utilities for Express Suite projects, including custom Jest matchers, console mocks, database helpers, and more.
|
|
163
|
+
|
|
164
|
+
### Test Utilities Overview
|
|
165
|
+
|
|
166
|
+
**Custom Matchers**: `toThrowType` for type-safe error testing
|
|
167
|
+
**Console Mocks**: Mock and spy on console methods
|
|
168
|
+
**Direct Log Mocks**: Mock `fs.writeSync` for stdout/stderr testing
|
|
169
|
+
**Database Helpers**: MongoDB Memory Server integration
|
|
170
|
+
**React Mocks**: Mock React components and hooks
|
|
171
|
+
|
|
172
|
+
### Usage Patterns
|
|
173
|
+
|
|
174
|
+
#### Using toThrowType Matcher
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import '@digitaldefiance/express-suite-test-utils';
|
|
178
|
+
|
|
179
|
+
class CustomError extends Error {
|
|
180
|
+
constructor(public code: number) {
|
|
181
|
+
super('Custom error');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
describe('Error Testing', () => {
|
|
186
|
+
it('should throw specific error type', () => {
|
|
187
|
+
expect(() => {
|
|
188
|
+
throw new CustomError(404);
|
|
189
|
+
}).toThrowType(CustomError);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should validate error properties', () => {
|
|
193
|
+
expect(() => {
|
|
194
|
+
throw new CustomError(404);
|
|
195
|
+
}).toThrowType(CustomError, (error) => {
|
|
196
|
+
expect(error.code).toBe(404);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### Using Console Mocks
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { withConsoleMocks, spyContains } from '@digitaldefiance/express-suite-test-utils';
|
|
206
|
+
|
|
207
|
+
describe('Console Output', () => {
|
|
208
|
+
it('should capture console.log', async () => {
|
|
209
|
+
await withConsoleMocks({ mute: true }, async (spies) => {
|
|
210
|
+
console.log('test message');
|
|
211
|
+
|
|
212
|
+
expect(spies.log).toHaveBeenCalledWith('test message');
|
|
213
|
+
expect(spyContains(spies.log, 'test', 'message')).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should capture console.error', async () => {
|
|
218
|
+
await withConsoleMocks({ mute: true }, async (spies) => {
|
|
219
|
+
console.error('error message');
|
|
220
|
+
|
|
221
|
+
expect(spies.error).toHaveBeenCalledWith('error message');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### Using Direct Log Mocks
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { withDirectLogMocks, directLogContains, getDirectLogMessages } from '@digitaldefiance/express-suite-test-utils';
|
|
231
|
+
import * as fs from 'fs';
|
|
232
|
+
|
|
233
|
+
// Mock fs at module level
|
|
234
|
+
jest.mock('fs', () => ({
|
|
235
|
+
...jest.requireActual('fs'),
|
|
236
|
+
writeSync: jest.fn(),
|
|
237
|
+
}));
|
|
238
|
+
|
|
239
|
+
describe('Direct Logging', () => {
|
|
240
|
+
it('should capture stdout writes', async () => {
|
|
241
|
+
await withDirectLogMocks({ mute: true }, async (spies) => {
|
|
242
|
+
const buffer = Buffer.from('hello world\n', 'utf8');
|
|
243
|
+
fs.writeSync(1, buffer); // stdout
|
|
244
|
+
|
|
245
|
+
expect(directLogContains(spies.writeSync, 1, 'hello', 'world')).toBe(true);
|
|
246
|
+
expect(getDirectLogMessages(spies.writeSync, 1)).toEqual(['hello world\n']);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### Using MongoDB Memory Server
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { connectMemoryDB, disconnectMemoryDB, clearMemoryDB } from '@digitaldefiance/express-suite-test-utils';
|
|
256
|
+
import { User } from './models/user';
|
|
257
|
+
|
|
258
|
+
describe('Database Tests', () => {
|
|
259
|
+
beforeAll(async () => {
|
|
260
|
+
await connectMemoryDB();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
afterAll(async () => {
|
|
264
|
+
await disconnectMemoryDB();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
afterEach(async () => {
|
|
268
|
+
await clearMemoryDB();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should validate user schema', async () => {
|
|
272
|
+
const user = new User({
|
|
273
|
+
username: 'test',
|
|
274
|
+
email: 'test@example.com'
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await user.validate(); // Real Mongoose validation!
|
|
278
|
+
await user.save();
|
|
279
|
+
|
|
280
|
+
const found = await User.findOne({ username: 'test' });
|
|
281
|
+
expect(found).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should reject invalid data', async () => {
|
|
285
|
+
const invalid = new User({ username: 'ab' }); // too short
|
|
286
|
+
|
|
287
|
+
await expect(invalid.validate()).rejects.toThrow();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Testing Best Practices
|
|
293
|
+
|
|
294
|
+
1. **Always clean up** after tests (disconnect DB, restore mocks)
|
|
295
|
+
2. **Use memory database** for fast, isolated tests
|
|
296
|
+
3. **Mock external dependencies** to avoid side effects
|
|
297
|
+
4. **Test error conditions** with `toThrowType` matcher
|
|
298
|
+
5. **Capture console output** when testing logging behavior
|
|
299
|
+
|
|
300
|
+
### Cross-Package Testing
|
|
301
|
+
|
|
302
|
+
These utilities are designed to work seamlessly with all Express Suite packages:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { connectMemoryDB, disconnectMemoryDB } from '@digitaldefiance/express-suite-test-utils';
|
|
306
|
+
import { Application } from '@digitaldefiance/node-express-suite';
|
|
307
|
+
import { UserService } from '@digitaldefiance/node-express-suite';
|
|
308
|
+
|
|
309
|
+
describe('Integration Tests', () => {
|
|
310
|
+
let app: Application;
|
|
311
|
+
|
|
312
|
+
beforeAll(async () => {
|
|
313
|
+
await connectMemoryDB();
|
|
314
|
+
app = new Application({
|
|
315
|
+
mongoUri: global.__MONGO_URI__,
|
|
316
|
+
jwtSecret: 'test-secret'
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
afterAll(async () => {
|
|
321
|
+
await app.stop();
|
|
322
|
+
await disconnectMemoryDB();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should create and find user', async () => {
|
|
326
|
+
const userService = new UserService(app);
|
|
327
|
+
const user = await userService.create({
|
|
328
|
+
username: 'alice',
|
|
329
|
+
email: 'alice@example.com'
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const found = await userService.findByUsername('alice');
|
|
333
|
+
expect(found).toBeDefined();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
100
338
|
## License
|
|
101
339
|
|
|
102
340
|
MIT
|
|
103
341
|
|
|
104
342
|
## ChangeLog
|
|
105
343
|
|
|
344
|
+
### v1.0.11
|
|
345
|
+
|
|
346
|
+
- Fix mongoose to use @digitaldefiance/mongoose-types
|
|
347
|
+
|
|
348
|
+
### v1.0.10
|
|
349
|
+
|
|
350
|
+
- Fix direct-log mocks to work with non-configurable fs.writeSync in newer Node.js versions
|
|
351
|
+
- Add comprehensive mongoose memory database testing utilities
|
|
352
|
+
- Fix memory mongoose connectMemoryDB
|
|
353
|
+
|
|
354
|
+
|
|
106
355
|
### v1.0.9
|
|
107
356
|
|
|
108
357
|
- Add mongoose memory helpers
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { Connection } from 'mongoose';
|
|
1
|
+
import { Connection } from '@digitaldefiance/mongoose-types';
|
|
2
2
|
/**
|
|
3
3
|
* Connect to in-memory MongoDB for testing
|
|
4
|
+
* @returns Object with both the connection and URI
|
|
4
5
|
*/
|
|
5
|
-
export declare function connectMemoryDB(): Promise<
|
|
6
|
+
export declare function connectMemoryDB(): Promise<{
|
|
7
|
+
connection: Connection;
|
|
8
|
+
uri: string;
|
|
9
|
+
}>;
|
|
6
10
|
/**
|
|
7
11
|
* Drop all collections and disconnect
|
|
8
12
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongoose-memory.d.ts","sourceRoot":"","sources":["../../../../../packages/digitaldefiance-express-suite-test-utils/src/lib/mongoose-memory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mongoose-memory.d.ts","sourceRoot":"","sources":["../../../../../packages/digitaldefiance-express-suite-test-utils/src/lib/mongoose-memory.ts"],"names":[],"mappings":"AAAA,OAAiB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAMvE;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC;IAC/C,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAsBD;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAWxD;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAOnD"}
|
|
@@ -4,22 +4,31 @@ exports.connectMemoryDB = connectMemoryDB;
|
|
|
4
4
|
exports.disconnectMemoryDB = disconnectMemoryDB;
|
|
5
5
|
exports.clearMemoryDB = clearMemoryDB;
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
|
+
const mongoose_types_1 = tslib_1.__importDefault(require("@digitaldefiance/mongoose-types"));
|
|
7
8
|
const mongodb_memory_server_1 = require("mongodb-memory-server");
|
|
8
|
-
const mongoose_1 = tslib_1.__importDefault(require("mongoose"));
|
|
9
9
|
let mongoServer;
|
|
10
10
|
let connection;
|
|
11
11
|
/**
|
|
12
12
|
* Connect to in-memory MongoDB for testing
|
|
13
|
+
* @returns Object with both the connection and URI
|
|
13
14
|
*/
|
|
14
15
|
async function connectMemoryDB() {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
// If mongoose is connected but we don't have our server, disconnect first
|
|
17
|
+
if (mongoose_types_1.default.connection.readyState !== 0 && !mongoServer) {
|
|
18
|
+
await mongoose_types_1.default.disconnect();
|
|
19
|
+
connection = undefined;
|
|
20
|
+
}
|
|
21
|
+
// Create new server if needed
|
|
22
|
+
if (!mongoServer) {
|
|
23
|
+
mongoServer = await mongodb_memory_server_1.MongoMemoryServer.create();
|
|
17
24
|
}
|
|
18
|
-
mongoServer = await mongodb_memory_server_1.MongoMemoryServer.create();
|
|
19
25
|
const uri = mongoServer.getUri();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
// Connect if not already connected
|
|
27
|
+
if (mongoose_types_1.default.connection.readyState === 0) {
|
|
28
|
+
await mongoose_types_1.default.connect(uri);
|
|
29
|
+
}
|
|
30
|
+
connection = mongoose_types_1.default.connection;
|
|
31
|
+
return { connection, uri };
|
|
23
32
|
}
|
|
24
33
|
/**
|
|
25
34
|
* Drop all collections and disconnect
|
|
@@ -27,7 +36,7 @@ async function connectMemoryDB() {
|
|
|
27
36
|
async function disconnectMemoryDB() {
|
|
28
37
|
if (connection) {
|
|
29
38
|
await connection.dropDatabase();
|
|
30
|
-
await
|
|
39
|
+
await mongoose_types_1.default.disconnect();
|
|
31
40
|
connection = undefined;
|
|
32
41
|
}
|
|
33
42
|
if (mongoServer) {
|