generate-jest-unit-tests
generate-jest-unit-tests
Title
Angular/Jest Unit Test Generator - Intelligent Jest unit test generation for Angular components, services, and more with Socratic planning and project-specific memory.
Version
v1.0.0 - Initial release
File Structure
Skill Files
forge-plugin/skills/generate-jest-unit-tests/
├── SKILL.md # This file
├── examples.md # Usage examples
├── scripts/
│ └── test_analyzer.ts # Helper for analyzing existing tests
└── templates/
├── component_test_template.txt # Component test structure
├── service_test_template.txt # Service test structure
└── test_case_template.txt # Individual test case template
Interface References
- ContextProvider —
getDomainIndex("angular"),getConditionalContext("angular", topic) - MemoryStore —
getSkillMemory("generate-jest-unit-tests", project),update()
Context (via ContextProvider)
contextProvider.getDomainIndex("angular")— Angular context navigationcontextProvider.getConditionalContext("angular", "jest_testing_standards")— Jest/Angular testing best practicescontextProvider.getConditionalContext("angular", "component_testing_patterns")— Component testing strategiescontextProvider.getConditionalContext("angular", "service_testing_patterns")— Service testing patternscontextProvider.getConditionalContext("angular", "testing_utilities")— TestBed, mocking, spies, utilitiescontextProvider.getConditionalContext("angular", "test_antipatterns")— What to avoid
Memory (via MemoryStore)
memoryStore.getSkillMemory("generate-jest-unit-tests", project)returns per-project files:testing_patterns.md— Project's testing conventionsexpected_behaviors.md— Known expected behaviorscommon_mocks.md— Reusable mocks and test dataframework_config.md— Jest/TestBed configuration
Required Reading
Context & Memory Loading (via Interfaces)
Before starting any test generation, load resources in this order:
-
Project Memory (via MemoryStore):
memoryStore.getSkillMemory("generate-jest-unit-tests", project)— loads all per-project files if they exist
-
Domain Index (via ContextProvider):
contextProvider.getDomainIndex("angular")— Angular context navigation
-
Core Testing Context (always load via ContextProvider):
contextProvider.getConditionalContext("angular", "jest_testing_standards")— Core testing principlescontextProvider.getConditionalContext("angular", "testing_utilities")— TestBed, mocking, spiescontextProvider.getConditionalContext("angular", "test_antipatterns")— What to avoid
-
Conditional Context (load based on code analysis):
- If component:
contextProvider.getConditionalContext("angular", "component_testing_patterns") - If service:
contextProvider.getConditionalContext("angular", "service_testing_patterns") - If NgRx:
contextProvider.getConditionalContext("angular", "ngrx_patterns") - If RxJS:
contextProvider.getConditionalContext("angular", "rxjs_patterns")
- If component:
Loading Order
CRITICAL: Resources must be loaded in this exact order:
1. Project memory via memoryStore (load project-specific patterns)
2. Domain index via contextProvider (understand available context)
3. Core context via contextProvider (testing standards, patterns)
4. Conditional context via contextProvider (based on code being tested)
Design Requirements
Core Principles
- Practical Tests: Generate tests that validate real behavior, not implementation details
- Maintainability: Tests should be easy to understand and update
- Non-Brittleness: Tests should not break on refactoring unless behavior changes
- Socratic Planning: Collaborate with user to understand expected behavior
- Project Memory: Learn and apply project-specific testing patterns
- Framework Awareness: Support Jest, Jasmine/Karma migration, Angular TestBed
- Angular-Specific: Handle components, services, pipes, directives, guards, resolvers
Test Quality Criteria
Generated tests must:
- ✅ Test component/service behavior, not implementation
- ✅ Have clear, descriptive names (should_X_when_Y)
- ✅ Follow AAA pattern (Arrange, Act, Assert)
- ✅ Be independent and isolated
- ✅ Use appropriate mocking (TestBed, jasmine spies, jest.mock)
- ✅ Handle Angular change detection properly
- ✅ Test observable streams correctly
- ✅ Include meaningful assertions
- ✅ Follow project conventions from memory
What NOT to Do
- ❌ Test private methods directly
- ❌ Create brittle tests tied to implementation
- ❌ Generate tests without understanding expected behavior
- ❌ Ignore existing project testing patterns
- ❌ Over-mock or under-mock
- ❌ Skip error/edge case scenarios
- ❌ Use generic test names
- ❌ Ignore change detection in component tests
- ❌ Not unsubscribe from observables in tests
Prompting Guidelines
User Interaction Phases
Phase 1: Initial Analysis
- Identify files to test (components, services, etc.)
- Analyze existing test structure
- Detect testing framework (Jest vs Jasmine/Karma)
- Identify Angular version and patterns
Phase 2: Socratic Planning (MANDATORY)
- Ask targeted questions about expected behavior
- Clarify edge cases and error scenarios
- Understand business logic and constraints
- Confirm testing approach
Phase 3: Test Generation
- Generate tests based on planning
- Apply project memory patterns
- Follow testing standards
Socratic Questions Framework
Ask questions in these categories:
-
Component Behavior (for components):
- "What should be displayed when [input changes]?"
- "How should the component react to [user interaction]?"
- "What should be emitted when [event occurs]?"
- "What lifecycle hooks are critical to test?"
-
Service Behavior (for services):
- "What should the service return when [method called]?"
- "How should errors be handled?"
- "Should HTTP calls be mocked?"
- "What observables need testing?"
-
Dependencies & Integration:
- "Should I create mock components for child components?"
- "Which services should be mocked vs real?"
- "Are there Router/ActivatedRoute dependencies?"
- "Does this use NgRx/state management?"
-
Angular-Specific:
- "Should I test with OnPush change detection?"
- "Are there template-driven or reactive forms?"
- "Does this component use @ViewChild/@ContentChild?"
- "Are there async operations to handle?"
-
Test Approach:
- "Shallow vs deep rendering for this component?"
- "Should I use TestBed or standalone component testing?"
- "Are there specific edge cases you're concerned about?"
Instructions
Mandatory Workflow (8 Steps)
This workflow is MANDATORY and NON-NEGOTIABLE. Every step must be completed in order.
Step 1: Initial Analysis
Purpose: Gather information about what needs to be tested.
Actions:
- Identify the Angular file(s) to test (component, service, pipe, directive, guard)
- Determine the project name from git repository or directory structure
- Check if tests already exist for the target files
- Identify the testing framework (Jest vs Jasmine/Karma)
- Detect Angular version (standalone components vs NgModule)
- List the classes/functions/methods that need test coverage
- Identify dependencies (services, router, HTTP, state management)
Validation:
- Target files identified and type determined (component/service/etc.)
- Project name determined
- Testing framework detected (Jest or Jasmine)
- Angular version and patterns identified
- Dependencies listed
Step 2: Load Index Files
Purpose: Understand what memory and context is available.
Actions:
- Load Angular domain index via
contextProvider.getDomainIndex("angular") - Identify which context topics will be needed based on file types
Validation:
- Domain index loaded
- Angular context map understood
- Relevant topics identified
Step 3: Load Project Memory
Purpose: Load project-specific testing patterns and conventions.
Actions:
- Load project memory via
memoryStore.getSkillMemory("generate-jest-unit-tests", project) - If memory exists, review all files:
testing_patterns.md- Project's testing conventionsexpected_behaviors.md- Known expected behaviorscommon_mocks.md- Reusable mocks and test dataframework_config.md- Jest/TestBed configuration
- If no memory exists, note that this is a new project (memory will be created later)
Validation:
- Project memory checked
- Existing patterns loaded (if available)
- Ready to create new memory (if needed)
Step 4: Load Context
Purpose: Load testing standards and best practices.
Actions:
-
Always load:
contextProvider.getConditionalContext("angular", "jest_testing_standards")contextProvider.getConditionalContext("angular", "testing_utilities")contextProvider.getConditionalContext("angular", "test_antipatterns")
-
Conditionally load (based on file type):
- If component:
contextProvider.getConditionalContext("angular", "component_testing_patterns") - If service:
contextProvider.getConditionalContext("angular", "service_testing_patterns") - If using NgRx:
contextProvider.getConditionalContext("angular", "ngrx_patterns") - If using RxJS heavily:
contextProvider.getConditionalContext("angular", "rxjs_patterns") - Use domain index from Step 2 as guide
- If component:
Validation:
- Core testing context loaded
- Type-specific context loaded (component/service)
- Framework-specific context loaded (if needed)
- Ready to apply testing standards
Step 5: Analyze Files to Test
Purpose: Understand the code structure and dependencies.
Actions:
- Read the target TypeScript file(s) completely
- Identify:
- Public methods/properties (primary test targets)
- Input/Output properties (for components)
- Component template interactions (for components)
- Service dependencies (injected via constructor)
- Observable streams and subscriptions
- Lifecycle hooks (ngOnInit, ngOnDestroy, etc.)
- Error handling patterns
- Complexity and edge cases
- Read existing tests (if any) to understand coverage gaps
- Analyze project structure to determine test file location
- Identify change detection strategy (OnPush vs Default)
Validation:
- Target code thoroughly understood
- Dependencies identified
- Observable patterns noted
- Edge cases identified
- Test file location determined
Step 6: Socratic Planning Phase
Purpose: Collaborate with the user to understand expected behavior.
CRITICAL: This step is MANDATORY. You MUST ask the user questions before generating tests.
Actions:
- Present a summary of what you analyzed
- Ask targeted questions using the Socratic Questions Framework:
- Component questions: Inputs, outputs, user interactions, display logic
- Service questions: Return values, error handling, HTTP mocking
- Dependency questions: What to mock, router usage, state management
- Angular-specific questions: Change detection, forms, ViewChild
- Testing approach questions: Shallow vs deep, TestBed setup
- Wait for user responses
- Use AskUserQuestion tool for structured multi-choice questions when appropriate
- Clarify ambiguities and confirm understanding
Example Questions:
I analyzed `UserProfileComponent` and found the following to test:
Component:
- @Input() userId: string
- @Output() profileUpdated = new EventEmitter<Profile>()
- Displays user profile data from UserService
- Has edit form with validation
- Uses OnPush change detection
Before generating tests, I need to understand the expected behavior:
1. **For profile loading**:
- Should I mock UserService.getProfile()?
- What should display while loading (spinner, placeholder)?
- How should it handle when userId is null/undefined?
2. **For profile editing**:
- What validations should I test (email format, required fields)?
- Should the form be disabled during save?
- What happens on save success vs error?
3. **For profileUpdated output**:
- When exactly should this emit (on save success)?
- What data should be emitted?
4. **Dependencies**:
- Mock UserService completely or use real service?
- Any router navigation to test?
5. **Testing approach**:
- Should I test with shallow rendering (mock child components)?
- Test template-driven or reactive form validation?
Validation:
- Summary presented to user
- At least 3-5 meaningful questions asked
- User responses received
- Expected behaviors clarified
- Testing approach confirmed
Step 7: Generate Unit Tests
Purpose: Create comprehensive, maintainable unit tests.
Actions:
- Create test file following project conventions:
- File naming:
{filename}.spec.ts(standard Angular convention) - Location: Co-located with source file or in separate test directory
- File naming:
- Apply templates from
./templates/ - For each component/service/class:
- Generate setup code (describe block, TestBed configuration)
- Generate happy path tests
- Generate edge case tests
- Generate error scenario tests
- Use AAA pattern (Arrange, Act, Assert)
- Include:
- Descriptive test names (should_X_when_Y)
- Appropriate mocks (jasmine.createSpyObj, jest.mock)
- Proper TestBed configuration
- Change detection triggers (for components)
- Observable testing (async/fakeAsync/done)
- Cleanup (unsubscribe, destroy)
- Apply project memory patterns and user's clarified behaviors
- Follow Jest vs Jasmine conventions based on detected framework
Component-Specific:
- Mock dependencies via TestBed providers
- Test template interactions (query selectors, click events)
- Test Input/Output properties
- Handle change detection (detectChanges, tick, flush)
- Mock child components if shallow rendering
Service-Specific:
- Mock HTTP calls (HttpTestingController for Jasmine, jest.mock for Jest)
- Test observable streams (subscribe, toPromise, firstValueFrom)
- Test error handling
- Test caching/state management
Validation:
- Test file created in correct location
- TestBed properly configured
- All public methods have test coverage
- Happy paths tested
- Edge cases tested
- Error scenarios tested
- Change detection handled (components)
- Observables tested properly
- Mocks configured correctly
- Project conventions followed
- User's expected behaviors implemented
Step 8: Update Project Memory
Purpose: Store learned patterns for future test generation.
Actions:
- Use
memoryStore.update("generate-jest-unit-tests", project, filename, content)for each file - Create or update memory files:
- testing_patterns.md: Document testing conventions observed/established
- Test file location pattern
- Naming conventions
- Testing framework (Jest vs Jasmine) and version
- TestBed configuration patterns
- Mock creation patterns
- Common test structure
- expected_behaviors.md: Document clarified behaviors from Socratic phase
- Component behaviors confirmed with user
- Service response patterns
- Error handling rules
- Edge case handling
- common_mocks.md: Document reusable mocks created
- Mock components
- Mock services
- Mock data/fixtures
- Spy configurations
- framework_config.md: Document testing framework configuration
- jest.config.js patterns
- TestBed common configurations
- Custom test utilities
- Test setup files
- testing_patterns.md: Document testing conventions observed/established
Validation:
- Project memory directory exists
- testing_patterns.md created/updated
- expected_behaviors.md created/updated
- common_mocks.md created/updated
- framework_config.md created/updated
Compliance Checklist
Before completing the skill invocation, verify ALL items:
Workflow Compliance
- Step 1: Initial Analysis completed
- Step 2: Index files loaded
- Step 3: Project memory loaded
- Step 4: Context loaded
- Step 5: Files analyzed
- Step 6: Socratic planning completed (questions asked and answered)
- Step 7: Tests generated
- Step 8: Memory updated
Test Quality
- Tests follow AAA pattern
- Test names are descriptive
- Appropriate mocking used (TestBed, spies)
- Change detection handled (components)
- Observables tested properly
- Edge cases covered
- Error scenarios covered
- Tests are independent
- Project conventions followed
Angular-Specific
- TestBed configured correctly
- Dependencies mocked appropriately
- Component fixtures created and managed
- Change detection triggered when needed
- Async operations handled (async/fakeAsync)
- Subscriptions cleaned up
Memory & Context
- Project memory checked and loaded
- New patterns documented in memory
- User-clarified behaviors stored
- Testing context applied
User Collaboration
- Socratic questions asked (minimum 3-5)
- User responses incorporated
- Expected behaviors confirmed
Best Practices
Test Organization
-
File Structure:
- Co-locate tests with source:
user.service.ts→user.service.spec.ts - Or separate directory:
src/app/services/→src/app/services/tests/ - Group related tests in describe blocks
- Co-locate tests with source:
-
Test Naming:
should_{expected_behavior}_when_{condition}- Be specific and descriptive
- Use clear describe block structure
-
Test Independence:
- Each test should run independently
- Use beforeEach for setup
- Use afterEach for cleanup
- Don't share state between tests
Angular Testing Patterns
Components:
describe('UserProfileComponent', () => {
let component: UserProfileComponent;
let fixture: ComponentFixture<UserProfileComponent>;
let mockUserService: jasmine.SpyObj<UserService>;
beforeEach(async () => {
mockUserService = jasmine.createSpyObj('UserService', ['getProfile', 'updateProfile']);
await TestBed.configureTestingModule({
declarations: [UserProfileComponent],
providers: [
{ provide: UserService, useValue: mockUserService }
]
}).compileComponents();
fixture = TestBed.createComponent(UserProfileComponent);
component = fixture.componentInstance;
});
it('should_load_user_profile_when_userId_provided', () => {
// Arrange
const mockProfile = { id: '123', name: 'Test User' };
mockUserService.getProfile.and.returnValue(of(mockProfile));
component.userId = '123';
// Act
fixture.detectChanges(); // Trigger ngOnInit
// Assert
expect(component.profile).toEqual(mockProfile);
expect(mockUserService.getProfile).toHaveBeenCalledWith('123');
});
});
Services:
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('should_return_user_when_getProfile_called', () => {
const mockUser = { id: '123', name: 'Test' };
service.getProfile('123').subscribe(user => {
expect(user).toEqual(mockUser);
});
const req = httpMock.expectOne('/api/users/123');
expect(req.request.method).toBe('GET');
req.flush(mockUser);
});
});
Mocking Strategy
-
What to Mock:
- External HTTP services (use HttpTestingController)
- Child components (use MockComponent or NO_ERRORS_SCHEMA)
- Router (use jasmine.createSpyObj)
- Services (use jasmine.createSpyObj or jest.mock)
- NgRx Store (use provideMockStore)
-
What NOT to Mock:
- Component/service under test
- Simple TypeScript classes
- Pure functions
- Angular built-ins (unless necessary)
Observable Testing
// Using async
it('should_emit_value_when_observable_completes', async(() => {
service.getData().subscribe(data => {
expect(data).toBe('test');
});
}));
// Using fakeAsync and tick
it('should_emit_after_delay', fakeAsync(() => {
let result: string;
service.getDelayedData().subscribe(data => result = data);
tick(1000);
expect(result).toBe('delayed');
}));
// Using done callback
it('should_complete_successfully', (done) => {
service.getData().subscribe({
next: data => expect(data).toBe('test'),
complete: done
});
});
Change Detection
// Trigger change detection
fixture.detectChanges();
// For OnPush components
component.changeDetectorRef.markForCheck();
fixture.detectChanges();
// Wait for async operations
await fixture.whenStable();
// In fakeAsync
tick();
fixture.detectChanges();
Additional Notes
Testing Frameworks
Jest (preferred for new projects):
- Fast execution
- Built-in mocking
- Snapshot testing
- Better TypeScript support
- Less boilerplate
Jasmine/Karma (Angular default):
- Traditional Angular testing
- Runs in real browser
- Good for E2E-like tests
- More verbose
Migration Considerations
When migrating from Jasmine to Jest:
- Replace
jasmine.createSpyObjwithjest.fn() - Replace
spyOn().and.returnValue()withjest.spyOn().mockReturnValue() - Replace
HttpTestingControllerwithjest.mock() - Update test configuration
Integration with Other Skills
- Before testing: Use
skill:angular-code-reviewto understand code quality - After testing: Review generated tests with
skill:angular-code-review - For changes: Use
skill:get-git-diffto see what changed and needs new tests
Common Patterns
Mock Component:
@Component({
selector: 'app-child',
template: ''
})
class MockChildComponent {
@Input() data: any;
@Output() action = new EventEmitter();
}
Spy Object:
const mockService = jasmine.createSpyObj('MyService', ['method1', 'method2'], {
property: 'value'
});
Test Async Pipe:
it('should_display_async_data', fakeAsync(() => {
component.data$ = of('test data');
fixture.detectChanges();
tick();
const element = fixture.nativeElement.querySelector('.data');
expect(element.textContent).toContain('test data');
}));
Version History
v1.1.0 (2025-07-15)
- Phase 4 Migration: Replaced hardcoded
../../context/and../../memory/paths with ContextProvider and MemoryStore interface calls - Added YAML frontmatter with context/memory declarations
- Added Interface References section
- Updated workflow steps to use contextProvider/memoryStore
v1.0.0 (2025-11-18)
- Initial release
- Mandatory 8-step workflow
- Socratic planning phase
- Project-specific memory system
- Centralized context integration
- Support for Jest and Jasmine/Karma
- Component and service test generation
- Template-based test generation