Testing and Troubleshooting Nx Plugins
Testing Generators
Unit Tests
Basic generator test:
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree, readProjectConfiguration } from '@nx/devkit';
import { libraryGenerator } from './generator';
describe('library generator', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should create library', async () => {
await libraryGenerator(tree, { name: 'test' });
const config = readProjectConfiguration(tree, 'test');
expect(config.root).toBe('libs/test');
expect(tree.exists('libs/test/src/index.ts')).toBeTruthy();
});
it('should handle directory option', async () => {
await libraryGenerator(tree, {
name: 'test',
directory: 'shared'
});
const config = readProjectConfiguration(tree, 'shared-test');
expect(config.root).toBe('libs/shared/test');
});
});
Dry-Run Validation
Test before applying:
npx nx g @my-org/my-plugin:library test --dry-run
Validate output structure:
npx nx g @my-org/my-plugin:library test --dry-run | grep -E "CREATE|UPDATE"
Testing Executors
Unit Tests
Basic executor test:
import { ExecutorContext } from '@nx/devkit';
import executor from './executor';
import { ExecutorSchema } from './schema';
const mockContext: ExecutorContext = {
root: '/workspace',
cwd: '/workspace',
projectName: 'test-project',
targetName: 'build',
workspace: { version: 2, projects: {} },
projectsConfigurations: {
version: 2,
projects: {
'test-project': {
root: 'apps/test-project'
}
}
},
isVerbose: false
};
describe('Build Executor', () => {
it('should execute successfully', async () => {
const options: ExecutorSchema = {
outputPath: 'dist/test'
};
const result = await executor(options, mockContext);
expect(result.success).toBe(true);
});
it('should fail with invalid options', async () => {
const options: ExecutorSchema = {
outputPath: ''
};
const result = await executor(options, mockContext);
expect(result.success).toBe(false);
});
});
Integration Tests
Test executor in workspace:
npx nx run my-project:my-target
Skip cache for testing:
npx nx run my-project:my-target --skip-nx-cache
Verify outputs:
npx nx run my-project:build
ls -la dist/apps/my-project
Common Issues
“Unable to resolve executor package reference”
Error:
NX Cannot find executor '@my-org/tools:build'
Unable to resolve @my-org/tools:build
Causes:
- Package not in workspaces
- Missing tsconfig path mapping
- No entry point file
- Missing main field
Solution:
- Check workspace inclusion in root
package.json:
{
"workspaces": [
"packages/*",
"tools/*"
]
}
- Verify path mapping in
tsconfig.base.json:
{
"compilerOptions": {
"paths": {
"@my-org/tools": ["tools/src/index.ts"]
}
}
}
- Create entry point
tools/src/index.ts:
export * from './executors/build/executor';
- Set main field in
tools/package.json:
{
"main": "./src/index.ts"
}
- Reset Nx daemon:
npx nx reset
npx nx run my-project:build
“Generator not found”
Error:
Cannot find generator '@my-org/my-plugin:library'
Solution:
- Check
generators.jsonregistration:
{
"generators": {
"library": {
"factory": "./src/generators/library/generator",
"schema": "./src/generators/library/schema.json"
}
}
}
- Verify
package.jsonpoints to registry:
{
"generators": "./generators.json"
}
- Check generator export:
export default async function libraryGenerator(tree: Tree, options: Schema) {
// ...
}
Schema validation errors
Error:
Required property 'name' is missing
Solution:
- Check schema.json required fields:
{
"required": ["name"]
}
- Provide default values:
{
"properties": {
"name": {
"type": "string",
"$default": {
"$source": "argv",
"index": 0
}
}
}
}
- Add prompts for interactive mode:
{
"properties": {
"name": {
"type": "string",
"x-prompt": "What name would you like to use?"
}
}
}
Cache not working
Symptoms:
- Executor runs every time
- No cache hits reported
Solution:
- Declare outputs in target config:
{
"targets": {
"build": {
"outputs": ["{options.outputPath}"]
}
}
}
- Ensure outputs are deterministic
- Check inputs configuration
- Verify executor returns
{ success: true }
Tree API violations
Error:
Cannot write file outside of workspace
Solution:
- Use Tree API exclusively:
tree.write()notfs.writeFileSync() - Keep all paths relative to workspace root
- Never bypass Tree with direct filesystem writes
Debugging Tips
Enable verbose output
npx nx run my-project:build --verbose
Reset Nx cache
npx nx reset
Check project configuration
npx nx show project my-project
Validate workspace
npx nx workspace-lint
Inspect task graph
npx nx graph
Search for implementation patterns
rg -n "generateFiles|tree.write|ExecutorContext" tools plugins
E2E Testing
Create test workspace:
import { execSync } from 'child_process';
import { rmSync } from 'fs';
describe('plugin e2e', () => {
const testWorkspace = 'test-workspace';
beforeAll(() => {
execSync(`npx create-nx-workspace ${testWorkspace} --preset=empty --no-interactive`);
execSync(`npm install @my-org/my-plugin`, { cwd: testWorkspace });
});
afterAll(() => {
rmSync(testWorkspace, { recursive: true, force: true });
});
it('should generate library', () => {
execSync(`npx nx g @my-org/my-plugin:library test`, {
cwd: testWorkspace,
stdio: 'inherit'
});
// Verify generated files
});
});
Performance Testing
Measure generator performance:
time npx nx g @my-org/my-plugin:library test --dry-run
Measure executor performance:
time npx nx run my-project:build --skip-nx-cache