feat: Adds unit test for generateTaskFiles and updates tests.mdc with new insights for effectively writing tests for an ES Module

This commit is contained in:
Eyal Toledano
2025-03-24 19:44:24 -04:00
parent cf75915208
commit 4690c99d4a
3 changed files with 353 additions and 30 deletions

View File

@@ -156,6 +156,117 @@ describe('Feature or Function Name', () => {
});
```
## ES Module Testing Strategies
When testing ES modules (`"type": "module"` in package.json), traditional mocking approaches require special handling to avoid reference and scoping issues.
- **Module Import Challenges**
- Functions imported from ES modules may still reference internal module-scoped variables
- Imported functions may not use your mocked dependencies even with proper jest.mock() setup
- ES module exports are read-only properties (cannot be reassigned during tests)
- **Mocking Entire Modules**
```javascript
// Mock the entire module with custom implementation
jest.mock('../../scripts/modules/task-manager.js', () => {
// Get original implementation for functions you want to preserve
const originalModule = jest.requireActual('../../scripts/modules/task-manager.js');
// Return mix of original and mocked functionality
return {
...originalModule,
generateTaskFiles: jest.fn() // Replace specific functions
};
});
// Import after mocks
import * as taskManager from '../../scripts/modules/task-manager.js';
// Now you can use the mock directly
const { generateTaskFiles } = taskManager;
```
- **Direct Implementation Testing**
- Instead of calling the actual function which may have module-scope reference issues:
```javascript
test('should perform expected actions', () => {
// Setup mocks for this specific test
mockReadJSON.mockImplementationOnce(() => sampleData);
// Manually simulate the function's behavior
const data = mockReadJSON('path/file.json');
mockValidateAndFixDependencies(data, 'path/file.json');
// Skip calling the actual function and verify mocks directly
expect(mockReadJSON).toHaveBeenCalledWith('path/file.json');
expect(mockValidateAndFixDependencies).toHaveBeenCalledWith(data, 'path/file.json');
});
```
- **Avoiding Module Property Assignment**
```javascript
// ❌ DON'T: This causes "Cannot assign to read only property" errors
const utils = await import('../../scripts/modules/utils.js');
utils.readJSON = mockReadJSON; // Error: read-only property
// ✅ DO: Use the module factory pattern in jest.mock()
jest.mock('../../scripts/modules/utils.js', () => ({
readJSON: mockReadJSONFunc,
writeJSON: mockWriteJSONFunc
}));
```
- **Handling Mock Verification Failures**
- If verification like `expect(mockFn).toHaveBeenCalled()` fails:
1. Check that your mock setup is before imports
2. Ensure you're using the right mock instance
3. Verify your test invokes behavior that would call the mock
4. Use `jest.clearAllMocks()` in beforeEach to reset mock state
5. Consider implementing a simpler test that directly verifies mock behavior
- **Full Example Pattern**
```javascript
// 1. Define mock implementations
const mockReadJSON = jest.fn();
const mockValidateAndFixDependencies = jest.fn();
// 2. Mock modules
jest.mock('../../scripts/modules/utils.js', () => ({
readJSON: mockReadJSON,
// Include other functions as needed
}));
jest.mock('../../scripts/modules/dependency-manager.js', () => ({
validateAndFixDependencies: mockValidateAndFixDependencies
}));
// 3. Import after mocks
import * as taskManager from '../../scripts/modules/task-manager.js';
describe('generateTaskFiles function', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should generate task files', () => {
// 4. Setup test-specific mock behavior
const sampleData = { tasks: [{ id: 1, title: 'Test' }] };
mockReadJSON.mockReturnValueOnce(sampleData);
// 5. Create direct implementation test
// Instead of calling: taskManager.generateTaskFiles('path', 'dir')
// Simulate reading data
const data = mockReadJSON('path');
expect(mockReadJSON).toHaveBeenCalledWith('path');
// Simulate other operations the function would perform
mockValidateAndFixDependencies(data, 'path');
expect(mockValidateAndFixDependencies).toHaveBeenCalledWith(data, 'path');
});
});
```
## Mocking Guidelines
- **File System Operations**