0
# Testing Utilities
1
2
Testing helpers for creating virtual workspaces and file systems for generator and executor testing. These utilities enable comprehensive testing of Nx generators, executors, and other workspace operations in isolated environments.
3
4
## Capabilities
5
6
### Tree Creation for Testing
7
8
Functions for creating virtual file systems in test environments.
9
10
```typescript { .api }
11
/**
12
* Create an empty virtual file system tree
13
* @returns Empty Tree instance for testing
14
*/
15
function createTree(): Tree;
16
17
/**
18
* Create a virtual tree with a basic empty workspace structure
19
* @returns Tree with minimal workspace files (nx.json, package.json, etc.)
20
*/
21
function createTreeWithEmptyWorkspace(): Tree;
22
23
/**
24
* @deprecated Use createTreeWithEmptyWorkspace instead
25
* Create a virtual tree with empty V1 workspace (throws error)
26
*/
27
function createTreeWithEmptyV1Workspace(): never;
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import {
34
createTree,
35
createTreeWithEmptyWorkspace,
36
Tree
37
} from "@nrwl/devkit/testing";
38
39
describe('My Generator', () => {
40
let tree: Tree;
41
42
beforeEach(() => {
43
// Create empty workspace for testing
44
tree = createTreeWithEmptyWorkspace();
45
});
46
47
it('should generate files correctly', async () => {
48
// Run your generator
49
await myGenerator(tree, { name: 'test-lib' });
50
51
// Assert files were created
52
expect(tree.exists('libs/test-lib/src/index.ts')).toBe(true);
53
expect(tree.exists('libs/test-lib/project.json')).toBe(true);
54
55
// Check file contents
56
const indexContent = tree.read('libs/test-lib/src/index.ts', 'utf-8');
57
expect(indexContent).toContain('export');
58
});
59
60
it('should update existing files', async () => {
61
// Pre-populate tree with existing files
62
tree.write('package.json', JSON.stringify({
63
name: 'test-workspace',
64
scripts: {}
65
}));
66
67
// Run generator that modifies existing files
68
await myGenerator(tree, { name: 'test-lib' });
69
70
// Check modifications
71
const packageJson = JSON.parse(tree.read('package.json', 'utf-8')!);
72
expect(packageJson.scripts).toBeDefined();
73
});
74
});
75
```
76
77
### Testing Patterns
78
79
Common patterns for testing generators and executors.
80
81
```typescript { .api }
82
/**
83
* Helper to run generator and capture results
84
* @param generator - Generator function to test
85
* @param tree - Virtual file system
86
* @param options - Generator options
87
* @returns Promise with generator result and updated tree
88
*/
89
async function runGenerator<T>(
90
generator: Generator<T>,
91
tree: Tree,
92
options: T
93
): Promise<{
94
tree: Tree;
95
callback?: GeneratorCallback;
96
}>;
97
98
/**
99
* Helper to test executor execution
100
* @param executor - Executor function to test
101
* @param options - Executor options
102
* @param context - Mock executor context
103
* @returns Promise with execution result
104
*/
105
async function runExecutor<T>(
106
executor: Executor<T>,
107
options: T,
108
context: Partial<ExecutorContext>
109
): Promise<{ success: boolean; [key: string]: any }>;
110
```
111
112
**Usage Examples:**
113
114
```typescript
115
import {
116
Tree,
117
Generator,
118
ExecutorContext,
119
createTreeWithEmptyWorkspace
120
} from "@nrwl/devkit";
121
import { runGenerator, runExecutor } from "@nrwl/devkit/testing";
122
123
// Testing a generator
124
describe('Library Generator', () => {
125
let tree: Tree;
126
127
beforeEach(() => {
128
tree = createTreeWithEmptyWorkspace();
129
});
130
131
it('should create library files', async () => {
132
const { tree: resultTree, callback } = await runGenerator(
133
libraryGenerator,
134
tree,
135
{ name: 'my-lib', directory: 'libs' }
136
);
137
138
// Test file creation
139
expect(resultTree.exists('libs/my-lib/src/index.ts')).toBe(true);
140
expect(resultTree.exists('libs/my-lib/project.json')).toBe(true);
141
142
// Test project configuration
143
const projectJson = JSON.parse(
144
resultTree.read('libs/my-lib/project.json', 'utf-8')!
145
);
146
expect(projectJson.projectType).toBe('library');
147
148
// Test callback execution
149
if (callback) {
150
await callback();
151
// Verify callback effects
152
}
153
});
154
155
it('should handle existing files gracefully', async () => {
156
// Pre-create conflicting file
157
tree.write('libs/my-lib/src/index.ts', 'existing content');
158
159
await expect(
160
runGenerator(libraryGenerator, tree, { name: 'my-lib' })
161
).rejects.toThrow('File already exists');
162
});
163
});
164
165
// Testing an executor
166
describe('Build Executor', () => {
167
it('should build successfully', async () => {
168
const mockContext: Partial<ExecutorContext> = {
169
root: '/workspace',
170
projectName: 'my-app',
171
targetName: 'build',
172
workspace: {
173
projects: {
174
'my-app': {
175
root: 'apps/my-app'
176
}
177
}
178
}
179
};
180
181
const result = await runExecutor(
182
buildExecutor,
183
{ outputPath: 'dist/apps/my-app' },
184
mockContext
185
);
186
187
expect(result.success).toBe(true);
188
});
189
190
it('should handle build errors', async () => {
191
const mockContext: Partial<ExecutorContext> = {
192
root: '/workspace',
193
projectName: 'broken-app'
194
};
195
196
const result = await runExecutor(
197
buildExecutor,
198
{ outputPath: 'dist/apps/broken-app' },
199
mockContext
200
);
201
202
expect(result.success).toBe(false);
203
expect(result.error).toBeDefined();
204
});
205
});
206
```
207
208
### Mock Utilities
209
210
Functions for creating mock objects and contexts for testing.
211
212
```typescript { .api }
213
/**
214
* Create a mock executor context for testing
215
* @param overrides - Properties to override in the context
216
* @returns Mock ExecutorContext
217
*/
218
function createMockExecutorContext(
219
overrides?: Partial<ExecutorContext>
220
): ExecutorContext;
221
222
/**
223
* Create a mock project graph for testing
224
* @param projects - Projects to include in the graph
225
* @returns Mock ProjectGraph
226
*/
227
function createMockProjectGraph(
228
projects: Record<string, ProjectConfiguration>
229
): ProjectGraph;
230
231
/**
232
* Create mock workspace configuration
233
* @param projects - Projects to include
234
* @returns Mock workspace configuration
235
*/
236
function createMockWorkspace(
237
projects: Record<string, ProjectConfiguration>
238
): ProjectsConfigurations;
239
```
240
241
**Usage Examples:**
242
243
```typescript
244
import {
245
createMockExecutorContext,
246
createMockProjectGraph,
247
createMockWorkspace
248
} from "@nrwl/devkit/testing";
249
250
describe('Workspace Operations', () => {
251
it('should work with mock project graph', () => {
252
const mockGraph = createMockProjectGraph({
253
'app1': {
254
root: 'apps/app1',
255
projectType: 'application'
256
},
257
'lib1': {
258
root: 'libs/lib1',
259
projectType: 'library'
260
}
261
});
262
263
// Test functions that depend on project graph
264
const dependencies = findProjectDependencies(mockGraph, 'app1');
265
expect(dependencies).toEqual(['lib1']);
266
});
267
268
it('should work with mock executor context', async () => {
269
const mockContext = createMockExecutorContext({
270
projectName: 'test-project',
271
targetName: 'build',
272
root: '/test-workspace'
273
});
274
275
const result = await myExecutor({ outputPath: 'dist' }, mockContext);
276
expect(result.success).toBe(true);
277
});
278
});
279
```
280
281
### Test Helpers for Common Scenarios
282
283
Specialized utilities for testing common Nx scenarios.
284
285
```typescript { .api }
286
/**
287
* Helper for testing file generation
288
* @param tree - Virtual file system
289
* @param generator - Generator to test
290
* @param options - Generator options
291
* @returns Test assertions helper
292
*/
293
function testFileGeneration<T>(
294
tree: Tree,
295
generator: Generator<T>,
296
options: T
297
): {
298
toHaveCreatedFile(path: string): void;
299
toHaveUpdatedFile(path: string): void;
300
toHaveFileContent(path: string, content: string | RegExp): void;
301
};
302
303
/**
304
* Helper for testing project configuration changes
305
* @param tree - Virtual file system
306
* @param projectName - Project to test
307
* @returns Project configuration assertions
308
*/
309
function testProjectConfiguration(tree: Tree, projectName: string): {
310
toHaveTarget(targetName: string): void;
311
toHaveTag(tag: string): void;
312
toHaveProjectType(type: 'application' | 'library'): void;
313
};
314
```
315
316
**Usage Examples:**
317
318
```typescript
319
import {
320
testFileGeneration,
321
testProjectConfiguration
322
} from "@nrwl/devkit/testing";
323
324
describe('Component Generator', () => {
325
let tree: Tree;
326
327
beforeEach(() => {
328
tree = createTreeWithEmptyWorkspace();
329
});
330
331
it('should generate component files', async () => {
332
const fileTest = testFileGeneration(
333
tree,
334
componentGenerator,
335
{ name: 'my-component', project: 'my-app' }
336
);
337
338
await fileTest.toHaveCreatedFile('apps/my-app/src/app/my-component/my-component.component.ts');
339
await fileTest.toHaveCreatedFile('apps/my-app/src/app/my-component/my-component.component.html');
340
await fileTest.toHaveFileContent(
341
'apps/my-app/src/app/my-component/my-component.component.ts',
342
/export class MyComponentComponent/
343
);
344
});
345
346
it('should configure project correctly', async () => {
347
await libraryGenerator(tree, { name: 'my-lib' });
348
349
const projectTest = testProjectConfiguration(tree, 'my-lib');
350
projectTest.toHaveTarget('build');
351
projectTest.toHaveTarget('test');
352
projectTest.toHaveProjectType('library');
353
projectTest.toHaveTag('scope:shared');
354
});
355
});
356
```