|
| 1 | +# Compose Test Guide |
| 2 | + |
| 3 | +The Compose test suite is mostly focused unit tests. Prefer small fakes and direct collaborators unless the test is specifically about command orchestration, component graph execution, process behavior, or a module-load seam. |
| 4 | + |
| 5 | +## Commands |
| 6 | + |
| 7 | +Run the full unit suite: |
| 8 | + |
| 9 | +```sh |
| 10 | +npm test |
| 11 | +``` |
| 12 | + |
| 13 | +Run focused unit tests: |
| 14 | + |
| 15 | +```sh |
| 16 | +npx mocha --require ./test/mocha/bootstrap.cjs --require ./test/mocha/root-hooks.cjs --timeout 10000 --node-option unhandled-rejections=strict "test/unit/src/components-service.test.js" |
| 17 | +``` |
| 18 | + |
| 19 | +Do not use `--config test/mocha/unit.cjs` for focused runs. That config includes `spec: ['test/**/*.test.js']`, so it runs the full suite even when a file path is passed. |
| 20 | + |
| 21 | +Run updated static checks before review: |
| 22 | + |
| 23 | +```sh |
| 24 | +npm run lint:updated |
| 25 | +npm run prettier-check:updated |
| 26 | +``` |
| 27 | + |
| 28 | +## Runtime Sandbox |
| 29 | + |
| 30 | +Mocha root hooks restore common process state before and after tests: |
| 31 | + |
| 32 | +| State | Covered | |
| 33 | +| ------------------------------------ | --------------------------------------- | |
| 34 | +| `sinon` stubs/spies/fakes | Yes | |
| 35 | +| `process.env` | Yes | |
| 36 | +| `process.argv` | Yes | |
| 37 | +| cwd | Yes, restored to the suite sandbox path | |
| 38 | +| `EvalError.$composeCommandStartTime` | Yes | |
| 39 | + |
| 40 | +The root hooks do not restore every global mutation. Keep local cleanup for: |
| 41 | + |
| 42 | +| State | Local cleanup required | |
| 43 | +| --------------------------------------------- | ---------------------- | |
| 44 | +| `require.cache` | Yes | |
| 45 | +| process listeners | Yes | |
| 46 | +| `process.platform` descriptors | Yes | |
| 47 | +| stream method overrides | Yes | |
| 48 | +| module singletons and static class properties | Yes | |
| 49 | + |
| 50 | +## Proxyquire |
| 51 | + |
| 52 | +`proxyquire` is acceptable when the test is about a module-boundary seam or require-time behavior. Do not use it just to avoid passing a plain fake to code that already accepts one. |
| 53 | + |
| 54 | +Good uses: |
| 55 | + |
| 56 | +| Seam | Example | |
| 57 | +| ------------------------------ | -------------------------------------------- | |
| 58 | +| Entrypoint loading | `bin/serverless-compose` | |
| 59 | +| Process listeners | `src/index.js` | |
| 60 | +| Child process spawning | `components/framework/index.js` | |
| 61 | +| AWS constructor config | S3 and CloudFormation constructor assertions | |
| 62 | +| Require-time terminal behavior | CLI colors, symbols, and reporters | |
| 63 | + |
| 64 | +When a file repeatedly loads the same module with the same seam, prefer a local helper: |
| 65 | + |
| 66 | +```js |
| 67 | +const loadSubject = (stubs) => proxyquire.noCallThru().load(modulePath, stubs); |
| 68 | +``` |
| 69 | + |
| 70 | +## Context Setup |
| 71 | + |
| 72 | +Use `Context` directly when the test is about `Context`. For larger orchestration tests, a helper is preferred if it reduces repeated setup without hiding the behavior under test. |
| 73 | + |
| 74 | +Prefer: |
| 75 | + |
| 76 | +```js |
| 77 | +const context = await createTestContext({ |
| 78 | + configuration: {}, |
| 79 | + stateStorage: { |
| 80 | + readComponentsOutputs: async () => ({}), |
| 81 | + }, |
| 82 | +}); |
| 83 | +``` |
| 84 | + |
| 85 | +Avoid hiding values that are central to the assertion, such as `stage`, service configuration, or state storage behavior. |
| 86 | + |
| 87 | +## AWS Test Doubles |
| 88 | + |
| 89 | +Use the mock style that matches the assertion. |
| 90 | + |
| 91 | +| Assertion | Preferred style | |
| 92 | +| ------------------------------ | ----------------------------- | |
| 93 | +| SDK command response or error | `aws-sdk-client-mock` | |
| 94 | +| Client constructor config | `proxyquire` constructor stub | |
| 95 | +| Credential provider resolution | `proxyquire` provider stubs | |
| 96 | +| S3 state read/write behavior | Direct fake `s3Client` object | |
| 97 | + |
| 98 | +Do not remove compatibility behavior such as `externalBucket`, `existingBucket`, or legacy `EU` region handling unless the product behavior is intentionally removed. |
| 99 | + |
| 100 | +## Filesystem Tests |
| 101 | + |
| 102 | +Use `test/lib/fs.js` helpers for file setup and teardown. The test suite already runs in a sandbox cwd. Use per-test temp directories when a test needs independent state or filesystem behavior under a specific root. |
| 103 | + |
| 104 | +## Review Checklist |
| 105 | + |
| 106 | +Before opening a PR: |
| 107 | + |
| 108 | +- [ ] No `describe.only`, `it.only`, `describe.skip`, or `it.skip` was introduced. |
| 109 | +- [ ] `proxyquire` use is intentional and, if repeated, centralized behind a helper. |
| 110 | +- [ ] Local cleanup remains for module cache, listeners, descriptors, stream overrides, and singletons. |
| 111 | +- [ ] Compatibility tests remain intact. |
| 112 | +- [ ] Targeted Mocha command passes. |
| 113 | +- [ ] Updated lint and prettier checks pass. |
0 commit comments