Skip to content

Commit 4562e4a

Browse files
authored
port learnings into testing workflow instructions (#977)
1 parent fd58076 commit 4562e4a

File tree

1 file changed

+25
-22
lines changed

1 file changed

+25
-22
lines changed

.github/instructions/testing-workflow.instructions.md

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ When implementing tests as an AI agent, choose between two main types:
5555

5656
### Primary Tool: `runTests`
5757

58-
Use the `runTests` tool to execute tests programmatically:
58+
Use the `runTests` tool to execute tests programmatically rather than terminal commands for better integration and result parsing:
5959

6060
```typescript
6161
// Run specific test files
@@ -80,7 +80,7 @@ await runTests({
8080

8181
### Compilation Requirements
8282

83-
Before running tests, ensure compilation:
83+
Before running tests, ensure compilation. Always start compilation with `npm run watch-tests` before test execution to ensure TypeScript files are built. Recompile after making import/export changes before running tests, as stubs won't work if they're applied to old compiled JavaScript that doesn't have the updated imports:
8484

8585
```typescript
8686
// Start watch mode for auto-compilation
@@ -100,7 +100,7 @@ await run_in_terminal({
100100

101101
### Alternative: Terminal Execution
102102

103-
For targeted test runs when `runTests` tool is unavailable:
103+
For targeted test runs when `runTests` tool is unavailable. Note: When a targeted test run yields 0 tests, first verify the compiled JS exists under `out/test` (rootDir is `src`); absence almost always means the test file sits outside `src` or compilation hasn't run yet:
104104

105105
```typescript
106106
// Run specific test suite
@@ -145,6 +145,8 @@ if (error.includes('AssertionError')) {
145145

146146
### Systematic Failure Analysis
147147

148+
Fix test issues iteratively - run tests, analyze failures, apply fixes, repeat until passing. When unit tests fail with VS Code API errors like `TypeError: X is not a constructor` or `Cannot read properties of undefined (reading 'Y')`, check if VS Code APIs are properly mocked in `/src/test/unittests.ts` - add missing APIs following the existing pattern.
149+
148150
```typescript
149151
interface TestFailureAnalysis {
150152
type: 'compilation' | 'runtime' | 'assertion' | 'timeout';
@@ -234,6 +236,8 @@ import * as sinon from 'sinon';
234236
import * as workspaceApis from '../../common/workspace.apis'; // Wrapper functions
235237

236238
// Stub wrapper functions, not VS Code APIs directly
239+
// Always mock wrapper functions (e.g., workspaceApis.getConfiguration()) instead of
240+
// VS Code APIs directly to avoid stubbing issues
237241
const mockGetConfiguration = sinon.stub(workspaceApis, 'getConfiguration');
238242
```
239243

@@ -372,6 +376,8 @@ interface MockWorkspaceConfig {
372376

373377
### Mock Setup Strategy
374378

379+
Create minimal mock objects with only required methods and use TypeScript type assertions (e.g., `mockApi as PythonEnvironmentApi`) to satisfy interface requirements instead of implementing all interface methods when only specific methods are needed for the test. Simplify mock setup by only mocking methods actually used in tests and use `as unknown as Type` for TypeScript compatibility.
380+
375381
```typescript
376382
suite('Function Integration Tests', () => {
377383
// 1. Declare all mocks
@@ -397,6 +403,8 @@ suite('Function Integration Tests', () => {
397403
mockGetWorkspaceFolders.returns(undefined);
398404

399405
// 5. Create mock configuration objects
406+
// When fixing mock environment creation, use null to truly omit
407+
// properties rather than undefined
400408
pythonConfig = {
401409
get: sinon.stub(),
402410
inspect: sinon.stub(),
@@ -447,6 +455,7 @@ const result = await getAllExtraSearchPaths();
447455
assert.deepStrictEqual(actual, expected, 'Should contain exactly the expected paths');
448456

449457
// Verify side effects
458+
// Use sinon.match() patterns for resilient assertions that don't break on minor output changes
450459
assert(mockTraceLog.calledWith(sinon.match(/completion/i)), 'Should log completion');
451460
});
452461
```
@@ -490,6 +499,15 @@ envConfig.inspect
490499
.returns({
491500
globalValue: ['/migrated/paths'],
492501
});
502+
503+
// Testing async functions with child processes:
504+
// Call the function first to get a promise, then use setTimeout to emit mock events,
505+
// then await the promise - this ensures proper timing of mock setup versus function execution
506+
507+
// Cannot stub internal function calls within the same module after import - stub external
508+
// dependencies instead (e.g., stub childProcessApis.spawnProcess rather than trying to stub
509+
// helpers.isUvInstalled when testing helpers.shouldUseUv) because intra-module calls use
510+
// direct references, not module exports
493511
```
494512

495513
## 🧪 Step 7: Test Categories and Patterns
@@ -499,6 +517,7 @@ envConfig.inspect
499517
- Test different setting combinations
500518
- Test setting precedence (workspace > user > default)
501519
- Test configuration errors and recovery
520+
- Always use dynamic path construction with Node.js `path` module when testing functions that resolve paths against workspace folders to ensure cross-platform compatibility
502521

503522
### Data Flow Tests
504523

@@ -543,31 +562,15 @@ envConfig.inspect
543562

544563
1. **Read test files** - Check structure and mock setup
545564
2. **Run tests** - Establish baseline functionality
546-
3. **Apply improvements** - Use patterns below
565+
3. **Apply improvements** - Use patterns below. When reviewing existing tests, focus on behavior rather than implementation details in test names and assertions
547566
4. **Verify** - Ensure tests still pass
548567

549568
### Common Fixes
550569

551570
- Over-complex mocks → Minimal mocks with only needed methods
552571
- Brittle assertions → Behavior-focused with error messages
553-
- Vague test names → Clear scenario descriptions
572+
- Vague test names → Clear scenario descriptions (transform "should return X when Y" into "should [expected behavior] when [scenario context]")
554573
- Missing structure → Mock → Run → Assert pattern
574+
- Untestable Node.js APIs → Create proxy abstraction functions (use function overloads to preserve intelligent typing while making functions mockable)
555575

556576
## 🧠 Agent Learnings
557-
558-
- Always use dynamic path construction with Node.js `path` module when testing functions that resolve paths against workspace folders to ensure cross-platform compatibility (1)
559-
- Use `runTests` tool for programmatic test execution rather than terminal commands for better integration and result parsing (1)
560-
- Mock wrapper functions (e.g., `workspaceApis.getConfiguration()`) instead of VS Code APIs directly to avoid stubbing issues (2)
561-
- Start compilation with `npm run watch-tests` before test execution to ensure TypeScript files are built (1)
562-
- Use `sinon.match()` patterns for resilient assertions that don't break on minor output changes (2)
563-
- Fix test issues iteratively - run tests, analyze failures, apply fixes, repeat until passing (1)
564-
- When fixing mock environment creation, use `null` to truly omit properties rather than `undefined` (1)
565-
- Always recompile TypeScript after making import/export changes before running tests, as stubs won't work if they're applied to old compiled JavaScript that doesn't have the updated imports (2)
566-
- Create proxy abstraction functions for Node.js APIs like `cp.spawn` to enable clean testing - use function overloads to preserve Node.js's intelligent typing while making the functions mockable (1)
567-
- When a targeted test run yields 0 tests, first verify the compiled JS exists under `out/test` (rootDir is `src`); absence almost always means the test file sits outside `src` or compilation hasn't run yet (1)
568-
- When unit tests fail with VS Code API errors like `TypeError: X is not a constructor` or `Cannot read properties of undefined (reading 'Y')`, check if VS Code APIs are properly mocked in `/src/test/unittests.ts` - add missing Task-related APIs (`Task`, `TaskScope`, `ShellExecution`, `TaskRevealKind`, `TaskPanelKind`) and namespace mocks (`tasks`) following the existing pattern of `mockedVSCode.X = vscodeMocks.vscMockExtHostedTypes.X` (1)
569-
- Create minimal mock objects with only required methods and use TypeScript type assertions (e.g., mockApi as PythonEnvironmentApi) to satisfy interface requirements instead of implementing all interface methods when only specific methods are needed for the test (1)
570-
- When reviewing existing tests, focus on behavior rather than implementation details in test names and assertions - transform "should return X when Y" into "should [expected behavior] when [scenario context]" (1)
571-
- Simplify mock setup by only mocking methods actually used in tests and use `as unknown as Type` for TypeScript compatibility (1)
572-
- When testing async functions that use child processes, call the function first to get a promise, then use setTimeout to emit mock events, then await the promise - this ensures proper timing of mock setup versus function execution (1)
573-
- Cannot stub internal function calls within the same module after import - stub external dependencies instead (e.g., stub `childProcessApis.spawnProcess` rather than trying to stub `helpers.isUvInstalled` when testing `helpers.shouldUseUv`) because intra-module calls use direct references, not module exports (1)

0 commit comments

Comments
 (0)