0
# Test Adapter
1
2
The TestAdapter provides a mock implementation of the Yeoman adapter interface, enabling precise control over user prompts and interactions during generator testing. It extends the base TestAdapter from `@yeoman/adapter` with additional yeoman-test specific functionality.
3
4
## Capabilities
5
6
### TestAdapter Creation
7
8
Create and configure TestAdapter instances for prompt mocking and interaction simulation.
9
10
```typescript { .api }
11
/**
12
* TestAdapter constructor with optional configuration
13
* @param options - Configuration options for the adapter
14
*/
15
class TestAdapter {
16
constructor(options?: TestAdapterOptions);
17
18
// Inherits from @yeoman/adapter TestAdapter
19
prompt<T extends PromptAnswers = PromptAnswers>(
20
questions: PromptQuestions<T>
21
): Promise<T>;
22
23
log: {
24
write: (message?: string) => boolean;
25
writeln: (message?: string) => boolean;
26
ok: (message?: string) => boolean;
27
error: (message?: string) => boolean;
28
skip: (message?: string) => boolean;
29
force: (message?: string) => boolean;
30
create: (message?: string) => boolean;
31
invoke: (message?: string) => boolean;
32
conflict: (message?: string) => boolean;
33
identical: (message?: string) => boolean;
34
info: (message?: string) => boolean;
35
table: (options: { head: string[]; rows: string[][] }) => void;
36
};
37
}
38
39
interface TestAdapterOptions {
40
/** Pre-configured answers for prompts */
41
mockedAnswers?: PromptAnswers;
42
43
/** Throw error if no answer provided for a prompt */
44
throwOnMissingAnswer?: boolean;
45
46
/** Callback function called for each prompt answer */
47
callback?: DummyPromptCallback;
48
49
/** Custom spy factory for creating mocks */
50
spyFactory?: (options: { returns?: any }) => any;
51
}
52
53
type DummyPromptCallback = (
54
this: any,
55
answer: any,
56
options: { question: any }
57
) => any;
58
59
type PromptAnswers = Record<string, any>;
60
type PromptQuestions<T> = Array<{
61
type?: string;
62
name: keyof T;
63
message: string;
64
default?: any;
65
choices?: any[];
66
validate?: Function;
67
filter?: Function;
68
when?: Function | boolean;
69
}>;
70
```
71
72
**Usage Examples:**
73
74
```typescript
75
import { TestAdapter } from "yeoman-test";
76
77
// Basic adapter
78
const adapter = new TestAdapter();
79
80
// Pre-configured adapter
81
const adapter = new TestAdapter({
82
mockedAnswers: {
83
projectName: "my-app",
84
features: ["typescript", "testing"],
85
confirmOverwrite: true
86
}
87
});
88
89
// Use with environment
90
const env = await helpers.createEnv({ adapter });
91
```
92
93
### Prompt Mocking
94
95
Configure automatic responses to generator prompts with validation and transformation.
96
97
```typescript { .api }
98
interface TestAdapterOptions {
99
/** Object mapping prompt names to their answers */
100
mockedAnswers?: PromptAnswers;
101
102
/** Throw error when a prompt has no mocked answer (default: false) */
103
throwOnMissingAnswer?: boolean;
104
105
/** Custom callback to process each prompt answer */
106
callback?: DummyPromptCallback;
107
}
108
109
type DummyPromptCallback = (
110
this: TestAdapter,
111
answer: any,
112
options: {
113
question: {
114
name: string;
115
message: string;
116
type?: string;
117
default?: any;
118
choices?: any[];
119
}
120
}
121
) => any;
122
```
123
124
**Usage Examples:**
125
126
```typescript
127
// Simple answer mocking
128
const adapter = new TestAdapter({
129
mockedAnswers: {
130
projectName: "test-project",
131
author: "Test Author",
132
features: ["feature1", "feature2"],
133
confirmInstall: false
134
},
135
throwOnMissingAnswer: true // Fail if unexpected prompts occur
136
});
137
138
// Advanced prompt processing
139
const adapter = new TestAdapter({
140
mockedAnswers: {
141
projectName: "My Project",
142
email: "test@example.com"
143
},
144
145
callback(answer, { question }) {
146
console.log(`Prompt: ${question.message}`);
147
console.log(`Raw answer: ${answer}`);
148
149
// Transform answers based on question type
150
switch (question.name) {
151
case "projectName":
152
// Convert to slug format
153
return answer.toLowerCase().replace(/\s+/g, "-");
154
155
case "email":
156
// Validate email format
157
if (!/\S+@\S+\.\S+/.test(answer)) {
158
throw new Error("Invalid email format");
159
}
160
return answer.toLowerCase();
161
162
default:
163
return answer;
164
}
165
}
166
});
167
```
168
169
### Logging Interface
170
171
The TestAdapter provides a comprehensive logging interface that can be mocked or monitored during tests.
172
173
```typescript { .api }
174
interface TestAdapterLogger {
175
/** Write message without newline */
176
write(message?: string): boolean;
177
178
/** Write message with newline */
179
writeln(message?: string): boolean;
180
181
/** Log success message */
182
ok(message?: string): boolean;
183
184
/** Log error message */
185
error(message?: string): boolean;
186
187
/** Log skip message */
188
skip(message?: string): boolean;
189
190
/** Log force message */
191
force(message?: string): boolean;
192
193
/** Log create message */
194
create(message?: string): boolean;
195
196
/** Log invoke message */
197
invoke(message?: string): boolean;
198
199
/** Log conflict message */
200
conflict(message?: string): boolean;
201
202
/** Log identical message */
203
identical(message?: string): boolean;
204
205
/** Log info message */
206
info(message?: string): boolean;
207
208
/** Display table */
209
table(options: { head: string[]; rows: string[][] }): void;
210
}
211
```
212
213
**Usage Examples:**
214
215
```typescript
216
import { TestAdapter } from "yeoman-test";
217
import { mock } from "node:test";
218
219
// Mock the logger for testing
220
const logSpy = mock.fn();
221
const adapter = new TestAdapter();
222
223
// Override log methods
224
adapter.log.ok = logSpy;
225
adapter.log.error = logSpy;
226
227
// Use in generator test
228
await helpers.run("my-generator", {}, { adapter });
229
230
// Verify logging behavior
231
expect(logSpy.mock.callCount()).toBeGreaterThan(0);
232
expect(logSpy.mock.calls[0].arguments[0]).toContain("Success");
233
```
234
235
### Prompt Interaction Testing
236
237
Test complex prompt flows and conditional prompts.
238
239
```typescript
240
// Test conditional prompts
241
const adapter = new TestAdapter({
242
mockedAnswers: {
243
useTypeScript: true,
244
tsConfigPath: "./tsconfig.json", // Only asked if useTypeScript is true
245
features: ["linting", "testing"],
246
customLinter: "eslint" // Only asked if "linting" is selected
247
},
248
249
callback(answer, { question }) {
250
// Log all prompt interactions for debugging
251
console.log(`Q: ${question.message}`);
252
console.log(`A: ${JSON.stringify(answer)}`);
253
return answer;
254
}
255
});
256
257
// Test validation failures
258
const adapter = new TestAdapter({
259
mockedAnswers: {
260
port: "3000",
261
email: "invalid-email" // Will trigger validation error
262
},
263
264
callback(answer, { question }) {
265
if (question.name === "email" && question.validate) {
266
const validationResult = question.validate(answer);
267
if (validationResult !== true) {
268
throw new Error(`Validation failed: ${validationResult}`);
269
}
270
}
271
return answer;
272
}
273
});
274
```
275
276
### Integration with RunContext
277
278
TestAdapter integrates seamlessly with RunContext through adapter options.
279
280
```typescript
281
import helpers from "yeoman-test";
282
283
// Create custom adapter
284
const adapter = new TestAdapter({
285
mockedAnswers: {
286
projectName: "test-app",
287
features: ["typescript"]
288
}
289
});
290
291
// Use with RunContext
292
await helpers.run("my-generator")
293
.withAdapterOptions({
294
mockedAnswers: {
295
additional: "answers" // Merged with adapter's answers
296
}
297
})
298
.withEnvironment((env) => {
299
// Override environment adapter
300
env.adapter = adapter;
301
});
302
303
// Alternative: Pass adapter through environment options
304
await helpers.run("my-generator", {}, {
305
adapter: adapter
306
});
307
```
308
309
## Advanced Usage Patterns
310
311
### Custom Spy Factory
312
313
```typescript
314
import { mock } from "node:test";
315
316
const adapter = new TestAdapter({
317
spyFactory: ({ returns }) => {
318
const spy = mock.fn();
319
if (returns !== undefined) {
320
spy.mockReturnValue(returns);
321
}
322
return spy;
323
}
324
});
325
326
// Custom spies will be used internally for prompt mocking
327
```
328
329
### Dynamic Answer Generation
330
331
```typescript
332
const adapter = new TestAdapter({
333
callback(answer, { question }) {
334
// Generate dynamic answers based on question
335
if (question.name === "timestamp") {
336
return new Date().toISOString();
337
}
338
339
if (question.name === "randomId") {
340
return Math.random().toString(36).substr(2, 9);
341
}
342
343
if (question.type === "list" && !answer) {
344
// Default to first choice for list questions
345
return question.choices[0];
346
}
347
348
return answer;
349
}
350
});
351
```
352
353
### Prompt Flow Testing
354
355
```typescript
356
describe("generator prompt flow", () => {
357
it("handles conditional prompts correctly", async () => {
358
const promptAnswers = [];
359
360
const adapter = new TestAdapter({
361
mockedAnswers: {
362
projectType: "library",
363
includeDocs: true,
364
docsFormat: "markdown" // Only asked if includeDocs is true
365
},
366
367
callback(answer, { question }) {
368
promptAnswers.push({ name: question.name, answer });
369
return answer;
370
}
371
});
372
373
await helpers.run("my-generator", {}, { adapter });
374
375
// Verify prompt sequence
376
expect(promptAnswers).toHaveLength(3);
377
expect(promptAnswers[0].name).toBe("projectType");
378
expect(promptAnswers[1].name).toBe("includeDocs");
379
expect(promptAnswers[2].name).toBe("docsFormat");
380
});
381
});
382
```
383
384
## Error Handling
385
386
The TestAdapter provides several error handling mechanisms:
387
388
```typescript
389
// Strict mode - fail on unexpected prompts
390
const strictAdapter = new TestAdapter({
391
throwOnMissingAnswer: true,
392
mockedAnswers: {
393
expected: "value"
394
}
395
});
396
397
// This will throw an error if any prompts other than "expected" are encountered
398
399
// Graceful fallbacks
400
const flexibleAdapter = new TestAdapter({
401
mockedAnswers: {
402
primary: "answer"
403
},
404
405
callback(answer, { question }) {
406
if (answer === undefined) {
407
// Provide fallback for unmocked prompts
408
switch (question.type) {
409
case "confirm":
410
return false;
411
case "input":
412
return question.default || "";
413
case "list":
414
return question.choices?.[0];
415
default:
416
return null;
417
}
418
}
419
return answer;
420
}
421
});
422
```
423
424
The TestAdapter provides comprehensive control over generator prompt interactions, enabling precise testing of user input scenarios and prompt flow validation.