Skip to main content

writing-tests

Write comprehensive tests for all components using Jest, Effect-TS, and test helpers. Use when creating new tests, adding test cases, or when the user asks to write tests, add test coverage, or test specific functionality.

跳到安装

来源信息

仓库
forcedotcom/apex-language-support
最近来源活动
2026年4月29日 18:44
检测到的 SKILL.md 语言
英语
星标
11
分支
3

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
writing-tests
description
Write comprehensive tests for all components using Jest, Effect-TS, and test helpers. Use when creating new tests, adding test cases, or when the user asks to write tests, add test coverage, or test specific functionality.
# Writing Tests ## Core Principles **When writing tests:** 1. Use Jest with `describe`/`it` blocks 2. Use Effect-TS for async operations (not async/await) 3. Test both valid and invalid cases 4. **Add debugging to production code** using `Effect.logDebug` in the code being tested 5. **Add debugging to test code** using `console.log` in tests 6. **Enable console logging while debugging** - Use `enableConsoleLogging()` and `setLogLevel('debug')` in `beforeEach`, then change to `setLogLevel('error')` when tests are ready 7. **Prefer real implementations over mocks** - Use real services, managers, and components unless mocking is absolutely necessary ## Test Structure ### Basic Test Template ```typescript import { MyComponent } from '../../src/components/MyComponent'; import { ApexSymbolManager } from '../../src/symbols/ApexSymbolManager'; import { CompilerService } from '../../src/parser/compilerService'; import { Effect } from 'effect'; import { enableConsoleLogging, setLogLevel } from '@salesforce/apex-lsp-shared'; describe('MyComponent', () => { let symbolManager: ApexSymbolManager; let compilerService: CompilerService; beforeEach(() => { symbolManager = new ApexSymbolManager(); compilerService = new CompilerService(); // Enable console logging - set to 'debug' while debugging, 'error' when ready enableConsoleLogging(); setLogLevel('error'); }); afterEach(() => { symbolManager.clear(); }); it('should do something correctly', async () => { // Test implementation const result = await doSomething(); expect(result).toBe(expectedValue); }); }); ``` ## Using Real Code vs Mocks **Prefer real implementations** - Tests should use real code whenever possible: ```typescript // ✅ Good - Use real implementations let symbolManager: ApexSymbolManager; let compilerService: CompilerService; beforeEach(() => { symbolManager = new ApexSymbolManager(); compilerService = new CompilerService(); }); ``` **Use mocks only when absolutely necessary:** - External services that can't be controlled (network calls, file system in some cases) - Code that has side effects that would break test isolation - Performance-critical tests where real implementations are too slow - Testing error conditions that are difficult to trigger with real code **Avoid mocks for:** - `ApexSymbolManager` - Use real instances - `CompilerService` - Use real instances - Internal services and components - Use real implementations - Effect services - Use real `EffectTestLoggerLive` and other live implementations - Internal types and utilities - Use real implementations **Example of acceptable mock usage:** ```typescript // Only mock when testing error conditions that are hard to trigger const mockService = { getData: jest.fn().mockRejectedValue(new Error('Network error')), } as unknown as MyService; ``` **Example of preferred real code:** ```typescript // Use real symbol manager - it's fast and provides better test coverage const symbolManager = new ApexSymbolManager(); await Effect.runPromise(symbolManager.addSymbolTable(symbolTable, uri)); ``` ## Effect-TS in Tests **Always use Effect.runPromise** (not async/await with Effect): ```typescript // ✅ Correct await Effect.runPromise(symbolManager.resolveCrossFileReferencesForFile(uri)); // ❌ Wrong await symbolManager.resolveCrossFileReferencesForFile(uri); ``` ## Debugging Tests **Two approaches for debugging:** 1. **Add debugging to production code** using Effect logging: ```typescript // In your production code, add Effect logging for debugging: yield * Effect.logDebug(`Processing ${items.length} items`); yield * Effect.logInfo('Operation started'); yield * Effect.logWarning('Potential issue detected'); yield * Effect.logError(`Operation failed: ${error.message}`); ``` 2. **Add debugging to test code** using `console.log`: ```typescript const result = await myFunction(); // Debug: Log results if test fails if (result !== expectedValue) { console.log('Unexpected result:', JSON.stringify(result, null, 2)); console.log('Expected:', expectedValue); } expect(result).toBe(expectedValue); ``` **Enable console logging while debugging tests:** ```typescript import { enableConsoleLogging, setLogLevel } from '@salesforce/apex-lsp-shared'; beforeEach(() => { symbolManager = new ApexSymbolManager(); compilerService = new CompilerService(); // Enable console logging and set to debug level while debugging enableConsoleLogging(); setLogLevel('debug'); }); ``` **When tests are ready, change log level to error:** ```typescript beforeEach(() => { symbolManager = new ApexSymbolManager(); compilerService = new CompilerService(); // Set to error level for production-ready tests enableConsoleLogging(); setLogLevel('error'); }); ``` **Note:** Effect logging from production code will appear in test output when console logging is enabled and log level is set appropriately. Ensure Effect services provide `EffectTestLoggerLive` or similar logging layers. ## Common Patterns ### Testing Valid Cases ```typescript it('should handle valid input correctly', async () => { const input = createValidInput(); const result = await myFunction(input); expect(result).toBeDefined(); expect(result.isValid).toBe(true); }); ``` ### Testing Invalid Cases ```typescript it('should handle invalid input correctly', async () => { const input = createInvalidInput(); const result = await myFunction(input); expect(result.isValid).toBe(false); expect(result.errors).toContain('Expected error message'); }); ``` ### Testing with Fixtures ```typescript import * as fs from 'fs'; import * as path from 'path'; const loadFixture = (filename: string): string => { const fixturePath = path.join(__dirname, '../fixtures', filename); return fs.readFileSync(fixturePath, 'utf8'); }; it('should process fixture correctly', async () => { const content = loadFixture('MyFixture.cls'); const result = await processContent(content); expect(result).toBeDefined(); }); ``` ## Test File Naming Test files follow the pattern: `{ComponentName}.test.ts` Examples: - `OperatorValidator.test.ts` - `ApexSymbolManager.getSymbolAtPosition.test.ts` - `SymbolReference.test.ts` - `ApexSymbolCollectorListener.assignment.test.ts` ## Anti-Patterns to Avoid **❌ Don't use async/await with Effect:** ```typescript // Bad const result = await myEffect; ``` **✅ Use Effect.runPromise:** ```typescript // Good const result = await Effect.runPromise(myEffect); ``` **❌ Don't forget to clean up resources:** ```typescript // Bad - missing afterEach beforeEach(() => { symbolManager = new ApexSymbolManager(); }); ``` **✅ Always clean up in afterEach:** ```typescript // Good afterEach(() => { symbolManager.clear(); }); ``` **❌ Don't mock when real code works:** ```typescript // Bad - unnecessary mock const mockSymbolManager = { getSymbol: jest.fn().mockReturnValue(symbol), } as ApexSymbolManager; ``` **✅ Use real implementations:** ```typescript // Good - real code provides better test coverage const symbolManager = new ApexSymbolManager(); await Effect.runPromise(symbolManager.addSymbolTable(symbolTable, uri)); ``` **❌ Don't leave debug logging enabled:** ```typescript // Bad - debug level left in production-ready test beforeEach(() => { enableConsoleLogging(); setLogLevel('debug'); // Should be 'error' when ready }); ``` **✅ Set log level to error when tests are ready:** ```typescript // Good - error level for production-ready tests beforeEach(() => { enableConsoleLogging(); setLogLevel('error'); }); ``` ## Validator-Specific Testing For validator tests, use helpers from `validation-test-helpers.ts`: ### compileFixtureWithOptions Compiles a fixture file and creates validation options: ```typescript import { compileFixtureWithOptions, runValidator, } from './helpers/validation-test-helpers'; const { symbolTable, options } = await compileFixtureWithOptions( VALIDATOR_CATEGORY, // e.g., 'operator', 'variable-resolution' 'MyFixture.cls', // Filename in test/fixtures/validation/{category}/ undefined, // Optional file URI symbolManager, compilerService, { tier: ValidationTier.IMMEDIATE, // or ValidationTier.THOROUGH allowArtifactLoading: false, // true for TIER 2 tests }, ); ``` ### runValidator Runs a validator Effect with all required services: ```typescript const result = await runValidator( MyValidator.validate(symbolTable, options), symbolManager, ); ``` **Returns:** `ValidationResult` with `isValid`, `errors`, and `warnings` arrays. **Note:** `runValidator` automatically provides `EffectTestLoggerLive`, so validators can use Effect logging (`Effect.logDebug`, `Effect.logInfo`, etc.). To see Effect logs in test output, enable console logging with `enableConsoleLogging()` and set an appropriate log level with `setLogLevel()`. ### Testing TIER 2 (THOROUGH) Validation TIER 2 tests require cross-file resolution: ```typescript describe('TIER 2: Cross-file type resolution', () => { it('should validate with resolved types', async () => { // Compile dependent files first await compileFixtureWithOptions( VALIDATOR_CATEGORY, 'DependentClass.cls', undefined, symbolManager, compilerService, { tier: ValidationTier.THOROUGH, allowArtifactLoading: true, }, ); // Compile the file under test const { symbolTable, options } = await compileFixtureWithOptions( VALIDATOR_CATEGORY, 'TestClass.cls', undefined, symbolManager, compilerService, { tier: ValidationTier.THOROUGH, allowArtifactLoading: true, }, ); // Resolve cross-file references await Effect.runPromise( symbolManager.resolveCrossFileReferencesForFile( symbolTable.getFileUri() || '', ), ); const result = await runValidator( MyValidator.validate(symbolTable, options), symbolManager, ); expect(result.isValid).toBe(true); expect(result.errors).toHaveLength(0); }); }); ``` ### Testing Error Codes For validator tests, check for specific error codes: ```typescript import { ErrorCodes } from '../../../../src/generated/ErrorCodes'; const hasError = result.errors.some( (e: any) => e.code === ErrorCodes.INVALID_COMPARISON_TYPES, ); expect(hasError).toBe(true); ``` ## Additional Resources - **Test Helpers**: See `test/semantics/validation/validators/helpers/validation-test-helpers.ts` for validator-specific helpers - **Error Codes**: See `src/generated/ErrorCodes.ts` for validator error codes - **Example Tests**: - Validators: `test/semantics/validation/validators/OperatorValidator.test.ts` - Symbol Manager: `test/symbols/ApexSymbolManager.getSymbolAtPosition.test.ts` - Types: `test/types/typeReference.test.ts` - **Effect-TS Testing**: See `prefer-effect-ts` skill for Effect patterns
在 GitHub 查看