@digitaldefiance/express-suite-test-utils 1.0.10 → 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
CHANGED
|
@@ -157,12 +157,194 @@ describe('User model', () => {
|
|
|
157
157
|
|
|
158
158
|
**Note:** Requires `mongoose` as a peer dependency and `mongodb-memory-server` as a dependency (already included).
|
|
159
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
|
+
|
|
160
338
|
## License
|
|
161
339
|
|
|
162
340
|
MIT
|
|
163
341
|
|
|
164
342
|
## ChangeLog
|
|
165
343
|
|
|
344
|
+
### v1.0.11
|
|
345
|
+
|
|
346
|
+
- Fix mongoose to use @digitaldefiance/mongoose-types
|
|
347
|
+
|
|
166
348
|
### v1.0.10
|
|
167
349
|
|
|
168
350
|
- Fix direct-log mocks to work with non-configurable fs.writeSync in newer Node.js versions
|
package/package.json
CHANGED
|
@@ -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,8 +4,8 @@ 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
|
/**
|
|
@@ -14,8 +14,8 @@ let connection;
|
|
|
14
14
|
*/
|
|
15
15
|
async function connectMemoryDB() {
|
|
16
16
|
// If mongoose is connected but we don't have our server, disconnect first
|
|
17
|
-
if (
|
|
18
|
-
await
|
|
17
|
+
if (mongoose_types_1.default.connection.readyState !== 0 && !mongoServer) {
|
|
18
|
+
await mongoose_types_1.default.disconnect();
|
|
19
19
|
connection = undefined;
|
|
20
20
|
}
|
|
21
21
|
// Create new server if needed
|
|
@@ -24,10 +24,10 @@ async function connectMemoryDB() {
|
|
|
24
24
|
}
|
|
25
25
|
const uri = mongoServer.getUri();
|
|
26
26
|
// Connect if not already connected
|
|
27
|
-
if (
|
|
28
|
-
await
|
|
27
|
+
if (mongoose_types_1.default.connection.readyState === 0) {
|
|
28
|
+
await mongoose_types_1.default.connect(uri);
|
|
29
29
|
}
|
|
30
|
-
connection =
|
|
30
|
+
connection = mongoose_types_1.default.connection;
|
|
31
31
|
return { connection, uri };
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
@@ -36,7 +36,7 @@ async function connectMemoryDB() {
|
|
|
36
36
|
async function disconnectMemoryDB() {
|
|
37
37
|
if (connection) {
|
|
38
38
|
await connection.dropDatabase();
|
|
39
|
-
await
|
|
39
|
+
await mongoose_types_1.default.disconnect();
|
|
40
40
|
connection = undefined;
|
|
41
41
|
}
|
|
42
42
|
if (mongoServer) {
|