Olino3

generate-jest-unit-tests

Intelligent Jest unit test generation for Angular components, services, and more with Socratic planning and project-specific memory.

Olino3 0 Updated 3mo ago

Resources

3
GitHub

Install

npx skillscat add olino3/forge/generate-jest-unit-tests

Install via the SkillsCat registry.

SKILL.md

Step 1: Initial Analysis

Purpose: Gather information about what needs to be tested.

Actions:

  1. Identify the Angular file(s) to test (component, service, pipe, directive, guard)
  2. Determine the project name from git repository or directory structure
  3. Check if tests already exist for the target files
  4. Identify the testing framework (Jest vs Jasmine/Karma)
  5. Detect Angular version (standalone components vs NgModule)
  6. List the classes/functions/methods that need test coverage
  7. 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:

  1. Load Angular domain index via contextProvider.getDomainIndex("angular")
  2. 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:

  1. Load project memory via memoryStore.getSkillMemory("generate-jest-unit-tests", project)
  2. If memory exists, review all files:
    • testing_patterns.md - Project's testing conventions
    • expected_behaviors.md - Known expected behaviors
    • common_mocks.md - Reusable mocks and test data
    • framework_config.md - Jest/TestBed configuration
  3. 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:

  1. Always load:

    • contextProvider.getConditionalContext("angular", "jest_testing_standards")
    • contextProvider.getConditionalContext("angular", "testing_utilities")
    • contextProvider.getConditionalContext("angular", "test_antipatterns")
  2. 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

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:

  1. Read the target TypeScript file(s) completely
  2. 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
  3. Read existing tests (if any) to understand coverage gaps
  4. Analyze project structure to determine test file location
  5. 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:

  1. Present a summary of what you analyzed
  2. 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
  3. Wait for user responses
  4. Use AskUserQuestion tool for structured multi-choice questions when appropriate
  5. 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:

  1. 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
  2. Apply templates from ./templates/
  3. 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)
  4. 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)
  5. Apply project memory patterns and user's clarified behaviors
  6. 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:

  1. Use memoryStore.update("generate-jest-unit-tests", project, filename, content) for each file
  2. 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

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

  1. File Structure:

    • Co-locate tests with source: user.service.tsuser.service.spec.ts
    • Or separate directory: src/app/services/src/app/services/tests/
    • Group related tests in describe blocks
  2. Test Naming:

    • should_{expected_behavior}_when_{condition}
    • Be specific and descriptive
    • Use clear describe block structure
  3. 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

  1. 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)
  2. 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.createSpyObj with jest.fn()
  • Replace spyOn().and.returnValue() with jest.spyOn().mockReturnValue()
  • Replace HttpTestingController with jest.mock()
  • Update test configuration

Integration with Other Skills

  • Before testing: Use skill:angular-code-review to understand code quality
  • After testing: Review generated tests with skill:angular-code-review
  • For changes: Use skill:get-git-diff to 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.