Intelligent Jest unit test generation for Angular components, services, and more with Socratic planning and project-specific memory.
Resources
3Install
npx skillscat add olino3/forge/generate-jest-unit-tests Install via the SkillsCat registry.
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
Purpose
Intelligent Jest unit test generation for Angular components, services, and more with Socratic planning and project-specific memory.
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
Step 1: Initial Analysis
Gather inputs and determine scope and requirements.
Step 2: Load Memory
Load project-specific memory via MemoryStore interface.
Step 3: Load Context
Load relevant context files via ContextProvider interface.
Step 4: Core Implementation
Execute the skill-specific core action.
Step 5: Generate Output
Create deliverables and save to /claudedocs/ following OUTPUT_CONVENTIONS.md.
Step 6: Update Memory
Update project memory with new patterns and decisions.