Skip to content

Commit

Permalink
test: implement integration tests
Browse files Browse the repository at this point in the history
These integration tests cover basic commands with multiple options
calling `this` for the command runner and the param handler
and having multiple commands inside the application. All passing
with great coverage.
  • Loading branch information
jmcdo29 committed Jan 21, 2021
1 parent 61be91d commit d725a75
Show file tree
Hide file tree
Showing 23 changed files with 340 additions and 21 deletions.
2 changes: 1 addition & 1 deletion integration/basic/src/basic.command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import { LogService } from './log.service';
import { LogService } from './../../common/log.service';

interface BasicCommandOptions {
string?: string;
Expand Down
2 changes: 1 addition & 1 deletion integration/basic/src/root.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { BasicCommand } from './basic.command';
import { LogService } from './log.service';
import { LogService } from './../../common/log.service';

@Module({
providers: [BasicCommand, LogService],
Expand Down
10 changes: 5 additions & 5 deletions integration/basic/test/basic.command.factory.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { TestingModule } from '@nestjs/testing';
import { CommandTestFactory } from 'nest-commander-testing';
import { LogService } from '../src/log.service';
import { LogService } from '../../common/log.service';
import { RootModule } from '../src/root.module';

describe('Basic Command', () => {
describe('Basic Command With Factory', () => {
const logMock = jest.fn();
let commandInstance: TestingModule;

const args = ['node', '/some/file.js', 'basic', 'test'];
const args = ['basic', 'test'];

beforeAll(async () => {
commandInstance = await CommandTestFactory.createTestingCommand({
Expand All @@ -18,7 +18,7 @@ describe('Basic Command', () => {
.compile();
});

describe.only('flags', () => {
describe('flags', () => {
it.each`
flagAndVal | expected
${['--string=hello']} | ${{ string: 'hello' }}
Expand All @@ -31,7 +31,7 @@ describe('Basic Command', () => {
'$flagAndVal \tlogs $expected',
async ({ flagAndVal, expected }: { flagAndVal: string[]; expected: Record<string, any> }) => {
await CommandTestFactory.run(commandInstance, [...args, ...flagAndVal]);
expect(logMock).toBeCalledWith({ params: ['test'], ...expected });
expect(logMock).toBeCalledWith({ param: ['test'], ...expected });
},
);
});
Expand Down
File renamed without changes.
14 changes: 14 additions & 0 deletions integration/multiple/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"private": "true",
"dependencies": {
"nest-commander": "workspace:*",
"nest-commander-testing": "workspace:*",
"commander": "^6.2.1",
"@nestjs/common": "^7.6.5",
"@nestjs/core": "^7.6.5",
"@nestjs/testing": "^7.6.5",
"jest": "^26.6.3",
"typescript": "^4.1.3",
"ts-jest": "^26.4.4"
}
}
11 changes: 11 additions & 0 deletions integration/multiple/src/bar.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Command, CommandRunner } from 'nest-commander';
import { LogService } from './../../common/log.service';

@Command({ name: 'bar', options: { isDefault: false } })
export class BarCommand implements CommandRunner {
constructor(private readonly logService: LogService) {}

async run(): Promise<void> {
this.logService.log('bar');
}
}
11 changes: 11 additions & 0 deletions integration/multiple/src/foo.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Command, CommandRunner } from 'nest-commander';
import { LogService } from './../../common/log.service';

@Command({ name: 'foo', options: { isDefault: true } })
export class FooCommand implements CommandRunner {
constructor(private readonly logService: LogService) {}

async run(): Promise<void> {
this.logService.log('foo');
}
}
9 changes: 9 additions & 0 deletions integration/multiple/src/root.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { BarCommand } from './bar.command';
import { FooCommand } from './foo.command';
import { LogService } from './../../common/log.service';

@Module({
providers: [BarCommand, FooCommand, LogService],
})
export class MultipleCommandModule {}
41 changes: 41 additions & 0 deletions integration/multiple/test/multiple.command.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { TestingModule } from '@nestjs/testing';
import { CommandTestFactory } from 'nest-commander-testing';
import { LogService } from '../../common/log.service';
import { MultipleCommandModule } from '../src/root.module';

describe('Multiple Commands', () => {
const logSpy = jest.fn();
let commandInstance: TestingModule;

beforeAll(async () => {
commandInstance = await CommandTestFactory.createTestingCommand({
imports: [MultipleCommandModule],
})
.overrideProvider(LogService)
.useValue({ log: logSpy })
.compile();
});

afterEach(() => {
logSpy.mockClear();
});

it.each`
command | expected
${'foo'} | ${'foo'}
${'bar'} | ${'bar'}
${undefined} | ${'foo'}
`(
'call $command expect $expected',
async ({
command,
expected,
}: {
command: 'bar' | 'foo' | undefined;
expected: 'bar' | 'foo';
}) => {
await CommandTestFactory.run(commandInstance, command ? [command] : undefined);
expect(logSpy).toBeCalledWith(expected);
},
);
});
14 changes: 14 additions & 0 deletions integration/this-command/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"private": "true",
"dependencies": {
"nest-commander": "workspace:*",
"nest-commander-testing": "workspace:*",
"commander": "^6.2.1",
"@nestjs/common": "^7.6.5",
"@nestjs/core": "^7.6.5",
"@nestjs/testing": "^7.6.5",
"jest": "^26.6.3",
"typescript": "^4.1.3",
"ts-jest": "^26.4.4"
}
}
15 changes: 15 additions & 0 deletions integration/this-command/src/this-command.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command, CommandRunner } from 'nest-commander';
import { LogService } from '../../common/log.service';

@Command({ name: 'this-command', arguments: '<with-value>' })
export class ThisCommandCommand implements CommandRunner {
constructor(private readonly log: LogService) {}

async run(params: string[]) {
this.logHandler(params);
}

logHandler(...args: any[]): void {
this.log.log(...args);
}
}
8 changes: 8 additions & 0 deletions integration/this-command/src/this-command.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common/decorators';
import { LogService } from '../../common/log.service';
import { ThisCommandCommand } from './this-command.command';

@Module({
providers: [LogService, ThisCommandCommand],
})
export class ThisCommandModule {}
25 changes: 25 additions & 0 deletions integration/this-command/test/this-command.command.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TestingModule } from '@nestjs/testing';
import { CommandTestFactory } from 'nest-commander-testing';
import { LogService } from '../../common/log.service';
import { ThisCommandModule } from '../src/this-command.module';

describe('This Handler', () => {
const logMock = jest.fn();
let commandModule: TestingModule;

beforeAll(async () => {
commandModule = await CommandTestFactory.createTestingCommand({ imports: [ThisCommandModule] })
.overrideProvider(LogService)
.useValue({ log: logMock })
.compile();
});

afterEach(() => {
logMock.mockClear();
});

it('should call this-handler with arg hello', async () => {
await CommandTestFactory.run(commandModule, ['this-command', 'hello']);
expect(logMock).toBeCalledWith(['hello']);
});
});
14 changes: 14 additions & 0 deletions integration/this-handler/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"private": "true",
"dependencies": {
"nest-commander": "workspace:*",
"nest-commander-testing": "workspace:*",
"commander": "^6.2.1",
"@nestjs/common": "^7.6.5",
"@nestjs/core": "^7.6.5",
"@nestjs/testing": "^7.6.5",
"jest": "^26.6.3",
"typescript": "^4.1.3",
"ts-jest": "^26.4.4"
}
}
32 changes: 32 additions & 0 deletions integration/this-handler/src/this-handler.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import { LogService } from '../../common/log.service';

@Command({
name: 'this-handler',
description: 'Just adding a description for coverage',
options: { isDefault: true },
})
export class ThisHandlerCommand implements CommandRunner {
constructor(private readonly log: LogService) {}

async run(params: string[], options: Record<string, string>) {
this.logHandler(options);
}

logHandler(...args: any[]): void {
this.log.log(...args);
}

@Option({
flags: '-b,--basic [basic]',
description: 'A value to pass for the this-handler method',
defaultValue: 'oh HAI',
})
parseBasicOption(val: string): string {
return this.handleParsingBasic(val);
}

handleParsingBasic(val: string): string {
return val;
}
}
8 changes: 8 additions & 0 deletions integration/this-handler/src/this-handler.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common/decorators';
import { LogService } from '../../common/log.service';
import { ThisHandlerCommand } from './this-handler.command';

@Module({
providers: [LogService, ThisHandlerCommand],
})
export class ThisHandlerModule {}
32 changes: 32 additions & 0 deletions integration/this-handler/test/this-handler.command.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { TestingModule } from '@nestjs/testing';
import { CommandTestFactory } from 'nest-commander-testing';
import { LogService } from '../../common/log.service';
import { ThisHandlerModule } from '../src/this-handler.module';

describe('This Handler', () => {
const logMock = jest.fn();
let commandModule: TestingModule;

beforeAll(async () => {
commandModule = await CommandTestFactory.createTestingCommand({ imports: [ThisHandlerModule] })
.overrideProvider(LogService)
.useValue({ log: logMock })
.compile();
});

afterEach(() => {
logMock.mockClear();
});

it.each`
flagVal | expected
${['-b']} | ${'oh HAI'}
${['-b', 'hello']} | ${'hello'}
`(
'should call this-handler with arg $flagVal and log $expected',
async ({ flagVal, expected }: { flagVal: string[]; expected: string }) => {
await CommandTestFactory.run(commandModule, ['this-handler'].concat(flagVal));
expect(logMock).toBeCalledWith({ basic: expected });
},
);
});
8 changes: 4 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module.exports = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: '.',
testRegex: '.spec.ts$',
testMatch: ['*.spec.ts'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
testEnvironment: 'node',
collectCoverageFrom: ['src/*'],
collectCoverage: true
};
collectCoverageFrom: ['packages/**/src/*.ts'],
collectCoverage: true,
};
13 changes: 11 additions & 2 deletions jest.integration.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
const baseConfig = require('./jest.config');
module.exports = {
...baseConfig,
testRegex: 'integration/.*.spec.ts$',
collectCoverage: false,
testMatch: ['<rootDir>/integration/**/*.spec.ts'],
collectCoverage: true,
verbose: true,
globals: {
'ts-jest': {
tsconfig: 'tsconfig.test.json',
},
},
moduleNameMapper: {
'^nest-commander$': ['<rootDir>/packages/nest-commander/src'],
'^nest-commander-testing$': ['<rootDir>/packages/nest-commander-testing/src'],
},
};
8 changes: 6 additions & 2 deletions packages/nest-commander-testing/src/command-test.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { Test, TestingModule, TestingModuleBuilder } from '@nestjs/testing';
import { randomBytes } from 'crypto';
import { CommandRunnerCoreModule, CommandRunnerCoreService } from 'nest-commander';

export type CommandModuleMetadata = Exclude<ModuleMetadata, 'imports'> & {
imports: NonNullable<ModuleMetadata['imports']>;
};

export class CommandTestFactory {
static createTestingCommand(moduleMetadata: ModuleMetadata): TestingModuleBuilder {
moduleMetadata.imports?.push(CommandRunnerCoreModule.forModule());
static createTestingCommand(moduleMetadata: CommandModuleMetadata): TestingModuleBuilder {
moduleMetadata.imports.push(CommandRunnerCoreModule.forModule());
return Test.createTestingModule(moduleMetadata);
}

Expand Down
Loading

0 comments on commit d725a75

Please sign in to comment.