diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..bce80b7 --- /dev/null +++ b/test/README.md @@ -0,0 +1,113 @@ +# Compose Test Guide + +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. + +## Commands + +Run the full unit suite: + +```sh +npm test +``` + +Run focused unit tests: + +```sh +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" +``` + +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. + +Run updated static checks before review: + +```sh +npm run lint:updated +npm run prettier-check:updated +``` + +## Runtime Sandbox + +Mocha root hooks restore common process state before and after tests: + +| State | Covered | +| ------------------------------------ | --------------------------------------- | +| `sinon` stubs/spies/fakes | Yes | +| `process.env` | Yes | +| `process.argv` | Yes | +| cwd | Yes, restored to the suite sandbox path | +| `EvalError.$composeCommandStartTime` | Yes | + +The root hooks do not restore every global mutation. Keep local cleanup for: + +| State | Local cleanup required | +| --------------------------------------------- | ---------------------- | +| `require.cache` | Yes | +| process listeners | Yes | +| `process.platform` descriptors | Yes | +| stream method overrides | Yes | +| module singletons and static class properties | Yes | + +## Proxyquire + +`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. + +Good uses: + +| Seam | Example | +| ------------------------------ | -------------------------------------------- | +| Entrypoint loading | `bin/serverless-compose` | +| Process listeners | `src/index.js` | +| Child process spawning | `components/framework/index.js` | +| AWS constructor config | S3 and CloudFormation constructor assertions | +| Require-time terminal behavior | CLI colors, symbols, and reporters | + +When a file repeatedly loads the same module with the same seam, prefer a local helper: + +```js +const loadSubject = (stubs) => proxyquire.noCallThru().load(modulePath, stubs); +``` + +## Context Setup + +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. + +Prefer: + +```js +const context = await createTestContext({ + configuration: {}, + stateStorage: { + readComponentsOutputs: async () => ({}), + }, +}); +``` + +Avoid hiding values that are central to the assertion, such as `stage`, service configuration, or state storage behavior. + +## AWS Test Doubles + +Use the mock style that matches the assertion. + +| Assertion | Preferred style | +| ------------------------------ | ----------------------------- | +| SDK command response or error | `aws-sdk-client-mock` | +| Client constructor config | `proxyquire` constructor stub | +| Credential provider resolution | `proxyquire` provider stubs | +| S3 state read/write behavior | Direct fake `s3Client` object | + +Do not remove compatibility behavior such as `externalBucket`, `existingBucket`, or legacy `EU` region handling unless the product behavior is intentionally removed. + +## Filesystem Tests + +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. + +## Review Checklist + +Before opening a PR: + +- [ ] No `describe.only`, `it.only`, `describe.skip`, or `it.skip` was introduced. +- [ ] `proxyquire` use is intentional and, if repeated, centralized behind a helper. +- [ ] Local cleanup remains for module cache, listeners, descriptors, stream overrides, and singletons. +- [ ] Compatibility tests remain intact. +- [ ] Targeted Mocha command passes. +- [ ] Updated lint and prettier checks pass. diff --git a/test/lib/private/rm-tmp-dir-ignorable-error-codes.js b/test/lib/private/rm-tmp-dir-ignorable-error-codes.js deleted file mode 100644 index 85d8abc..0000000 --- a/test/lib/private/rm-tmp-dir-ignorable-error-codes.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = new Set(['EBUSY', 'EPERM']); diff --git a/test/lib/process-tmp-dir.js b/test/lib/process-tmp-dir.js deleted file mode 100644 index 614a264..0000000 --- a/test/lib/process-tmp-dir.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const { mkdirSync, realpathSync, rmSync } = require('fs'); -const path = require('path'); -const os = require('os'); -const crypto = require('crypto'); -const rmTmpDirIgnorableErrorCodes = require('./private/rm-tmp-dir-ignorable-error-codes'); - -const systemTmpDir = realpathSync(os.tmpdir()); -const composeTmpDir = path.join(systemTmpDir, 'tmpdirs-compose'); -try { - mkdirSync(composeTmpDir); -} catch (error) { - if (error.code !== 'EEXIST') throw error; -} - -module.exports = (function self() { - const processTmpDir = path.join(composeTmpDir, crypto.randomBytes(2).toString('hex')); - try { - mkdirSync(processTmpDir); - } catch (error) { - if (error.code !== 'EEXIST') throw error; - return self(); - } - return processTmpDir; -})(); - -process.on('exit', () => { - try { - rmSync(module.exports, { recursive: true, force: true }); - } catch (error) { - if (rmTmpDirIgnorableErrorCodes.has(error.code)) return; - throw error; - } -}); diff --git a/test/unit/src/components-service.test.js b/test/unit/src/components-service.test.js index fc5d649..5893185 100644 --- a/test/unit/src/components-service.test.js +++ b/test/unit/src/components-service.test.js @@ -26,6 +26,57 @@ const frameworkComponentPath = path.dirname( require.resolve('../../../components/framework/index.js') ); +const createContext = async ({ + root = process.cwd(), + stage = 'dev', + disableIO = true, + configuration = {}, + stateStorage, +} = {}) => { + const context = new Context({ root, stage, disableIO, configuration }); + await context.init(); + + if (stateStorage) context.stateStorage = stateStorage; + + return context; +}; + +const loadComponentsService = ({ loadComponent, FrameworkComponent } = {}) => { + const stubs = {}; + + if (loadComponent) { + stubs['./load'] = { loadComponent }; + } + + if (FrameworkComponent) { + stubs['../components/framework'] = FrameworkComponent; + } + + if (Object.keys(stubs).length === 0) return ComponentsService; + + return proxyquire('../../../src/ComponentsService', stubs); +}; + +const createComponentsService = async ({ + configuration, + options = {}, + context, + stateStorage, + loadComponent, + FrameworkComponent, +}) => { + const localContext = context || (await createContext({ stateStorage })); + const ComponentsServiceClass = loadComponentsService({ loadComponent, FrameworkComponent }); + const localComponentsService = new ComponentsServiceClass(localContext, configuration, options); + await localComponentsService.init(); + + return { componentsService: localComponentsService, context: localContext }; +}; + +const createEmptyOutputsStateStorage = () => ({ + readComponentsOutputs: async () => ({}), +}); + describe('test/unit/src/components-service.test.js', () => { let componentsService; before(async () => { @@ -48,14 +99,7 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }; - const contextConfig = { - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }; - const context = new Context(contextConfig); - await context.init(); + const context = await createContext(); componentsService = new ComponentsService(context, configuration, {}); await componentsService.init(); }); @@ -95,13 +139,7 @@ describe('test/unit/src/components-service.test.js', () => { }); it('does not treat inherited object keys as internal components', async () => { - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); + const context = await createContext(); const localComponentsService = new ComponentsService( context, @@ -147,14 +185,7 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }; - const contextConfig = { - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }; - const context = new Context(contextConfig); - await context.init(); + const context = await createContext(); componentsService = new ComponentsService(context, configuration, {}); await expect(componentsService.init()).to.eventually.be.rejectedWith( @@ -178,13 +209,7 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }; - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); + const context = await createContext(); const localComponentsService = new ComponentsService(context, configuration, {}); @@ -204,13 +229,7 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }; - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); + const context = await createContext(); const localComponentsService = new ComponentsService(context, configuration, {}); @@ -221,13 +240,7 @@ describe('test/unit/src/components-service.test.js', () => { }); it('rejects reserved service aliases during initialization', async () => { - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); + const context = await createContext(); const localComponentsService = new ComponentsService( context, @@ -256,22 +269,6 @@ describe('test/unit/src/components-service.test.js', () => { commands: {}, alias, })); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - context.stateStorage.readComponentsOutputs = async () => ({ - foundation: { - endpoint: 'https://example.com', - }, - }); - const configuration = { services: { foundation: { @@ -289,8 +286,17 @@ describe('test/unit/src/components-service.test.js', () => { }, }; - const localComponentsService = new ComponentsServiceWithStubbedLoad(context, configuration, {}); - await localComponentsService.init(); + const { componentsService: localComponentsService } = await createComponentsService({ + configuration, + loadComponent, + stateStorage: { + readComponentsOutputs: async () => ({ + foundation: { + endpoint: 'https://example.com', + }, + }), + }, + }); await expect(localComponentsService.deploy()).to.eventually.be.rejected.and.have.property( 'code', @@ -307,22 +313,6 @@ describe('test/unit/src/components-service.test.js', () => { commands: {}, alias, })); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - context.stateStorage.readComponentsOutputs = async () => ({ - foundation: { - items: ['one', 'two', 'three'], - }, - }); - const configuration = { services: { foundation: { @@ -340,8 +330,17 @@ describe('test/unit/src/components-service.test.js', () => { }, }; - const localComponentsService = new ComponentsServiceWithStubbedLoad(context, configuration, {}); - await localComponentsService.init(); + const { componentsService: localComponentsService } = await createComponentsService({ + configuration, + loadComponent, + stateStorage: { + readComponentsOutputs: async () => ({ + foundation: { + items: ['one', 'two', 'three'], + }, + }), + }, + }); await localComponentsService.deploy(); expect(loadComponent.secondCall.args[0].inputs.params.count).to.equal(3); @@ -354,19 +353,6 @@ describe('test/unit/src/components-service.test.js', () => { order.push(alias); }, })); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - context.stateStorage.readComponentsOutputs = async () => ({}); - const configuration = { services: { foundation: { @@ -386,8 +372,11 @@ describe('test/unit/src/components-service.test.js', () => { }, }; - const localComponentsService = new ComponentsServiceWithStubbedLoad(context, configuration, {}); - await localComponentsService.init(); + const { componentsService: localComponentsService } = await createComponentsService({ + configuration, + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), + }); await localComponentsService.deploy(); expect(order).to.deep.equal(['foundation', 'api', 'app']); @@ -400,20 +389,6 @@ describe('test/unit/src/components-service.test.js', () => { order.push(alias); }, })); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - context.stateStorage.readComponentsOutputs = async () => ({}); - context.stateStorage.removeState = async () => {}; - const configuration = { services: { foundation: { @@ -433,8 +408,14 @@ describe('test/unit/src/components-service.test.js', () => { }, }; - const localComponentsService = new ComponentsServiceWithStubbedLoad(context, configuration, {}); - await localComponentsService.init(); + const { componentsService: localComponentsService } = await createComponentsService({ + configuration, + loadComponent, + stateStorage: { + readComponentsOutputs: async () => ({}), + removeState: async () => {}, + }, + }); await localComponentsService.remove(); expect(order).to.deep.equal(['app', 'api', 'foundation']); @@ -447,19 +428,6 @@ describe('test/unit/src/components-service.test.js', () => { order.push(alias); }, })); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - context.stateStorage.readComponentsOutputs = async () => ({}); - const configuration = { services: { foundation: { @@ -484,8 +452,11 @@ describe('test/unit/src/components-service.test.js', () => { }, }; - const localComponentsService = new ComponentsServiceWithStubbedLoad(context, configuration, {}); - await localComponentsService.init(); + const { componentsService: localComponentsService } = await createComponentsService({ + configuration, + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), + }); await localComponentsService.deploy(); const foundationIndex = order.indexOf('foundation'); @@ -511,19 +482,6 @@ describe('test/unit/src/components-service.test.js', () => { context.progresses.success(alias, 'deployed'); }, })); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - context.stateStorage.readComponentsOutputs = async () => ({}); - const configuration = { services: { foundation: { @@ -543,8 +501,11 @@ describe('test/unit/src/components-service.test.js', () => { }, }; - const localComponentsService = new ComponentsServiceWithStubbedLoad(context, configuration, {}); - await localComponentsService.init(); + const { componentsService: localComponentsService, context } = await createComponentsService({ + configuration, + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), + }); await localComponentsService.deploy(); expect(order).to.deep.equal(['foundation', 'api']); @@ -571,19 +532,6 @@ describe('test/unit/src/components-service.test.js', () => { active -= 1; }, })); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - context.stateStorage.readComponentsOutputs = async () => ({}); - const configuration = { services: { foundation: { @@ -601,10 +549,12 @@ describe('test/unit/src/components-service.test.js', () => { }, }; - const localComponentsService = new ComponentsServiceWithStubbedLoad(context, configuration, { - 'max-concurrency': 2, + const { componentsService: localComponentsService } = await createComponentsService({ + configuration, + options: { 'max-concurrency': 2 }, + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), }); - await localComponentsService.init(); const infoPromise = localComponentsService.info({ 'max-concurrency': 2 }); @@ -634,12 +584,6 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }; - const contextConfig = { - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }; const mockedStateStorage = { readServiceState: () => ({ id: 123, detectedFrameworkVersion: '9.9.9' }), readComponentsOutputs: () => { @@ -654,9 +598,7 @@ describe('test/unit/src/components-service.test.js', () => { }; }, }; - const context = new Context(contextConfig); - await context.init(); - context.stateStorage = mockedStateStorage; + const context = await createContext({ stateStorage: mockedStateStorage }); componentsService = new ComponentsService(context, configuration, {}); await componentsService.outputs(); @@ -685,12 +627,6 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }; - const contextConfig = { - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }; const mockedStateStorage = { readServiceState: () => ({ id: 123, detectedFrameworkVersion: '9.9.9' }), readComponentOutputs: () => { @@ -699,9 +635,7 @@ describe('test/unit/src/components-service.test.js', () => { }; }, }; - const context = new Context(contextConfig); - await context.init(); - context.stateStorage = mockedStateStorage; + const context = await createContext({ stateStorage: mockedStateStorage }); componentsService = new ComponentsService(context, configuration, {}); await componentsService.outputs({ componentName: 'resources' }); @@ -711,16 +645,11 @@ describe('test/unit/src/components-service.test.js', () => { }); it('rejects global outputs command when no deployed service outputs exist', async () => { - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, + const context = await createContext({ + stateStorage: { + readComponentsOutputs: () => Object.create(null), + }, }); - await context.init(); - context.stateStorage = { - readComponentsOutputs: () => Object.create(null), - }; const localComponentsService = new ComponentsService( context, @@ -738,21 +667,16 @@ describe('test/unit/src/components-service.test.js', () => { }); it('renders non-empty null-prototype outputs', async () => { - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, + const context = await createContext({ + stateStorage: { + readComponentsOutputs: () => + Object.assign(Object.create(null), { + resources: { + endpoint: 'https://example.com', + }, + }), + }, }); - await context.init(); - context.stateStorage = { - readComponentsOutputs: () => - Object.assign(Object.create(null), { - resources: { - endpoint: 'https://example.com', - }, - }), - }; const localComponentsService = new ComponentsService( context, @@ -771,17 +695,8 @@ describe('test/unit/src/components-service.test.js', () => { }); it('does not add graph edges for services with no dependencies', async () => { - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - - const localComponentsService = new ComponentsService( - context, - { + const { componentsService: localComponentsService } = await createComponentsService({ + configuration: { name: 'test-service', services: { resources: { @@ -792,10 +707,7 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }, - {} - ); - - await localComponentsService.init(); + }); expect(localComponentsService.allComponents.resources.dependencies).to.deep.equal([]); expect(localComponentsService.allComponents.api.dependencies).to.deep.equal([]); @@ -804,27 +716,13 @@ describe('test/unit/src/components-service.test.js', () => { it('returns without loading components when graph execution has no nodes', async () => { const loadComponent = sinon.stub(); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - - const localComponentsService = new ComponentsServiceWithStubbedLoad( - context, - { + const { componentsService: localComponentsService } = await createComponentsService({ + configuration: { name: 'test-service', services: {}, }, - {} - ); - - await localComponentsService.init(); + loadComponent, + }); await localComponentsService.executeComponentsGraph({ method: 'deploy' }); await localComponentsService.instantiateComponents(); @@ -832,13 +730,7 @@ describe('test/unit/src/components-service.test.js', () => { }); it('rejects reserved component aliases in direct command invocation', async () => { - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); + const context = await createContext(); const localComponentsService = new ComponentsService(context, { services: {} }, {}); await expect( @@ -850,20 +742,8 @@ describe('test/unit/src/components-service.test.js', () => { const loadComponent = sinon.stub().resolves({ commands: {}, }); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - - const localComponentsService = new ComponentsServiceWithStubbedLoad( - context, - { + const { componentsService: localComponentsService } = await createComponentsService({ + configuration: { services: { foundation: { component: '@foo/foundation', @@ -871,9 +751,9 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }, - {} - ); - await localComponentsService.init(); + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), + }); await expect( localComponentsService.invokeComponentCommand('foundation', 'toString', {}) @@ -889,20 +769,8 @@ describe('test/unit/src/components-service.test.js', () => { }, }), }); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - - const localComponentsService = new ComponentsServiceWithStubbedLoad( - context, - { + const { componentsService: localComponentsService } = await createComponentsService({ + configuration: { services: { foundation: { component: '@foo/foundation', @@ -910,9 +778,9 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }, - {} - ); - await localComponentsService.init(); + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), + }); await expect( localComponentsService.invokeComponentCommand('foundation', 'inherited', {}) @@ -928,20 +796,8 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - - const localComponentsService = new ComponentsServiceWithStubbedLoad( - context, - { + const { componentsService: localComponentsService } = await createComponentsService({ + configuration: { services: { foundation: { component: '@foo/foundation', @@ -949,9 +805,9 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }, - {} - ); - await localComponentsService.init(); + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), + }); await expect( localComponentsService.invokeComponentCommand('foundation', 'broken', {}) @@ -968,30 +824,18 @@ describe('test/unit/src/components-service.test.js', () => { } const loadComponent = sinon.stub().resolves(new FakeServerlessFramework()); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - '../components/framework': FakeServerlessFramework, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - - const localComponentsService = new ComponentsServiceWithStubbedLoad( - context, - { + const { componentsService: localComponentsService, context } = await createComponentsService({ + configuration: { services: { api: { path: 'api', }, }, }, - {} - ); - await localComponentsService.init(); + loadComponent, + FrameworkComponent: FakeServerlessFramework, + stateStorage: createEmptyOutputsStateStorage(), + }); const cases = [ { @@ -1037,20 +881,8 @@ describe('test/unit/src/components-service.test.js', () => { } const loadComponent = sinon.stub().resolves(new FakeComponent()); - const ComponentsServiceWithStubbedLoad = proxyquire('../../../src/ComponentsService', { - './load': { loadComponent }, - }); - const context = new Context({ - root: process.cwd(), - stage: 'dev', - disableIO: true, - configuration: {}, - }); - await context.init(); - - const localComponentsService = new ComponentsServiceWithStubbedLoad( - context, - { + const { componentsService: localComponentsService, context } = await createComponentsService({ + configuration: { services: { foundation: { component: '@foo/foundation', @@ -1058,9 +890,9 @@ describe('test/unit/src/components-service.test.js', () => { }, }, }, - {} - ); - await localComponentsService.init(); + loadComponent, + stateStorage: createEmptyOutputsStateStorage(), + }); await localComponentsService.invokeComponentCommand('foundation', 'deploy', { force: true }); expect(deploy.calledOnceWithExactly({ force: true })).to.equal(true);