Skip to content

Commit

Permalink
feat(core): add test project generator (#69)
Browse files Browse the repository at this point in the history
* feat(core): add test project generator

Add a generator that creates test projects for existing apps/libs

Co-authored-by: Ben Callaghan <bcallaghan@selectbankcard.com>
  • Loading branch information
bcallaghan-et and Ben Callaghan authored Jul 5, 2021
1 parent cfc2e20 commit 7f7084f
Show file tree
Hide file tree
Showing 11 changed files with 402 additions and 77 deletions.
5 changes: 5 additions & 0 deletions packages/core/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
"factory": "./src/generators/restore/generator",
"schema": "./src/generators/restore/schema.json",
"description": "Restores NuGet packages and .NET tools used by the workspace"
},
"test": {
"factory": "./src/generators/test/generator",
"schema": "./src/generators/test/schema.json",
"description": "Generate a .NET test project for an existing application or library"
}
}
}
44 changes: 44 additions & 0 deletions packages/core/src/generators/test/generator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';

import { DotNetClient, mockDotnetFactory } from '@nx-dotnet/dotnet';

import * as mockedProjectGenerator from '../utils/generate-test-project';
import generator from './generator';
import { NxDotnetGeneratorSchema } from './schema';

jest.mock('../utils/generate-test-project');

describe('nx-dotnet test generator', () => {
let appTree: Tree;
let dotnetClient: DotNetClient;

const options: NxDotnetGeneratorSchema = {
project: 'existing',
testTemplate: 'xunit',
language: 'C#',
skipOutputPathManipulation: true,
};

beforeEach(() => {
appTree = createTreeWithEmptyWorkspace();
dotnetClient = new DotNetClient(mockDotnetFactory());
});

it('should run successfully', async () => {
await generator(appTree, options, dotnetClient);
});

it('should call project generator with application project type', async () => {
const projectGenerator = (mockedProjectGenerator as jest.Mocked<
typeof mockedProjectGenerator
>).GenerateTestProject;

await generator(appTree, options, dotnetClient);
expect(projectGenerator).toHaveBeenCalledWith(
appTree,
options,
dotnetClient,
);
});
});
14 changes: 14 additions & 0 deletions packages/core/src/generators/test/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Tree } from '@nrwl/devkit';

import { DotNetClient, dotnetFactory } from '@nx-dotnet/dotnet';

import { GenerateTestProject } from '../utils/generate-test-project';
import { NxDotnetGeneratorSchema } from './schema';

export default function (
host: Tree,
options: NxDotnetGeneratorSchema,
dotnetClient = new DotNetClient(dotnetFactory()),
) {
return GenerateTestProject(host, options, dotnetClient);
}
3 changes: 3 additions & 0 deletions packages/core/src/generators/test/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { NxDotnetTestGeneratorSchema } from '../../models';

export type NxDotnetGeneratorSchema = NxDotnetTestGeneratorSchema;
48 changes: 48 additions & 0 deletions packages/core/src/generators/test/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"id": "@nx-dotnet/core:lib",
"title": "NxDotnet Test Generator",
"description": "Generate a .NET test project for an existing application or library",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The existing project to generate tests for",
"$default": {
"$source": "argv",
"index": 0
}
},
"testTemplate": {
"type": "string",
"description": "Which template should be used for creating the tests project?",
"default": "nunit",
"enum": ["nunit", "xunit", "mstest"],
"x-prompt": {
"message": "Which template should be used for creating the tests project",
"type": "list",
"items": [
{ "value": "nunit", "label": "NUnit 3 Test Project" },
{ "value": "xunit", "label": "xUnit Test Project" },
{ "value": "mstest", "label": "Unit Test Project" }
]
}
},
"language": {
"type": "string",
"description": "Which language should the project use?",
"x-prompt": {
"message": "Which language should the project use?",
"type": "list",
"items": ["C#", "F#", "VB"]
}
},
"skipOutputPathManipulation": {
"type": "boolean",
"description": "Skip XML changes for default build path",
"default": false
}
},
"required": ["name", "testTemplate"]
}
13 changes: 0 additions & 13 deletions packages/core/src/generators/utils/generate-project.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,6 @@ describe('nx-dotnet project generator', () => {
expect(config.targets.serve).not.toBeDefined();
});

it('should tag generated projects', async () => {
await GenerateProject(appTree, options, dotnetClient, 'library');
const config = readProjectConfiguration(appTree, 'test');
expect(config.tags).toContain('nx-dotnet');
});

it('should run successfully for applications', async () => {
await GenerateProject(appTree, options, dotnetClient, 'application');
const config = readProjectConfiguration(appTree, 'test');
Expand Down Expand Up @@ -102,13 +96,6 @@ describe('nx-dotnet project generator', () => {
expect(config.targets.lint).toBeDefined();
});

it('should include lint target in test project', async () => {
options.testTemplate = 'nunit';
await GenerateProject(appTree, options, dotnetClient, 'application');
const config = readProjectConfiguration(appTree, 'test-test');
expect(config.targets.lint).toBeDefined();
});

it('should prepend directory name to project name', async () => {
options.directory = 'sub-dir';
const spy = spyOn(dotnetClient, 'new');
Expand Down
94 changes: 30 additions & 64 deletions packages/core/src/generators/utils/generate-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
NxJsonProjectConfiguration,
ProjectConfiguration,
ProjectType,
readProjectConfiguration,
readWorkspaceConfiguration,
Tree,
} from '@nrwl/devkit';
Expand All @@ -25,12 +26,13 @@ import {
GetBuildExecutorConfiguration,
GetLintExecutorConfiguration,
GetServeExecutorConfig,
GetTestExecutorConfig,
NxDotnetProjectGeneratorSchema,
NxDotnetTestGeneratorSchema,
} from '../../models';
import initSchematic from '../init/generator';
import { GenerateTestProject } from './generate-test-project';

interface NormalizedSchema extends NxDotnetProjectGeneratorSchema {
export interface NormalizedSchema extends NxDotnetProjectGeneratorSchema {
projectName: string;
projectRoot: string;
projectDirectory: string;
Expand All @@ -39,13 +41,32 @@ interface NormalizedSchema extends NxDotnetProjectGeneratorSchema {
parsedTags: string[];
className: string;
namespaceName: string;
projectType: ProjectType;
}

function normalizeOptions(
export function normalizeOptions(
host: Tree,
options: NxDotnetProjectGeneratorSchema,
projectType: ProjectType,
options: NxDotnetProjectGeneratorSchema | NxDotnetTestGeneratorSchema,
projectType?: ProjectType,
): NormalizedSchema {
if (!('name' in options)) {
// Reconstruct the original parameters as if the test project were generated at the same time as the target project.
const project = readProjectConfiguration(host, options.project);
const projectPaths = project.root.split('/');
const directory = projectPaths.slice(1, -1).join('/'); // The middle portions contain the original path.
const [name] = projectPaths.slice(-1); // The final folder contains the original name.

options = {
name,
language: options.language,
skipOutputPathManipulation: options.skipOutputPathManipulation,
testTemplate: options.testTemplate,
directory,
tags: project.tags?.join(','),
} as NxDotnetProjectGeneratorSchema;
projectType = project.projectType;
}

const name = names(options.name).fileName;
const className = names(options.name).className;
const projectDirectory = options.directory
Expand Down Expand Up @@ -79,61 +100,11 @@ function normalizeOptions(
projectLanguage: options.language,
projectTemplate: options.template,
namespaceName,
projectType: projectType ?? 'library',
};
}

async function GenerateTestProject(
schema: NormalizedSchema,
host: Tree,
dotnetClient: DotNetClient,
projectType: ProjectType,
) {
const testRoot = schema.projectRoot + '-test';
const testProjectName = schema.projectName + '-test';

addProjectConfiguration(host, testProjectName, {
root: testRoot,
projectType: projectType,
sourceRoot: `${testRoot}`,
targets: {
build: GetBuildExecutorConfiguration(testRoot),
test: GetTestExecutorConfig(),
lint: GetLintExecutorConfiguration(),
},
tags: schema.parsedTags,
});

const newParams: dotnetNewOptions = [
{
flag: 'language',
value: schema.language,
},
{
flag: 'name',
value: schema.namespaceName + '.Test',
},
{
flag: 'output',
value: schema.projectRoot + '-test',
},
];

if (isDryRun()) {
addDryRunParameter(newParams);
}

dotnetClient.new(schema.testTemplate, newParams);

if (!isDryRun() && !schema.skipOutputPathManipulation) {
const testCsProj = await findProjectFileInPath(testRoot);
SetOutputPath(host, testRoot, testCsProj);
const baseCsProj = await findProjectFileInPath(schema.projectRoot);
SetOutputPath(host, schema.projectRoot, baseCsProj);
dotnetClient.addProjectReference(testCsProj, baseCsProj);
}
}

function SetOutputPath(
export function SetOutputPath(
host: Tree,
projectRootPath: string,
projectFilePath: string,
Expand Down Expand Up @@ -227,12 +198,7 @@ export async function GenerateProject(
dotnetClient.new(normalizedOptions.template, newParams);

if (options['testTemplate'] !== 'none') {
await GenerateTestProject(
normalizedOptions,
host,
dotnetClient,
projectType,
);
await GenerateTestProject(host, normalizedOptions, dotnetClient);
} else if (!options.skipOutputPathManipulation) {
SetOutputPath(
host,
Expand All @@ -244,7 +210,7 @@ export async function GenerateProject(
await formatFiles(host);
}

function addDryRunParameter(parameters: dotnetNewOptions): void {
export function addDryRunParameter(parameters: dotnetNewOptions): void {
parameters.push({
flag: 'dryRun',
value: true,
Expand Down
Loading

0 comments on commit 7f7084f

Please sign in to comment.