You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 4多测试用例运行时触发ZoneAwareError问题求助

Fixing ZoneAwareError in Angular 4 Test Cases (State Pollution & Async Handling)

Let's break down why your second test fails when run alongside the first, and how to fix it:

The Root Cause

Your issue boils down to cross-test state pollution combined with a small misstep in async test handling:

  1. Your SettingsService mock is relying on a shared external object (settingsServiceMockSettings) that doesn't get reset between tests. When the first test runs, it leaves the mock in its default state, and by the time you update it in the second test, your component has already finished initializing and isn't picking up the change.
  2. The await on de.triggerEventHandler isn't actually waiting for async logic in your component to finish—triggerEventHandler doesn't return a promise, so your assertions run before the component's double-click logic completes.

Step-by-Step Fixes

1. Isolate Mock State for Each Test

Stop using a shared configuration object. Instead, create a fresh mock setup inside your beforeEach so every test starts with a clean state:

describe('Component: ArticleComponent', () => { 
    let httpService; 
    let alertService; 
    let stateService; 
    let settingsService; 
    let windowRefService; 
    let fixture;
    let comp;
    let de;
    let el;
    let articleComponent;
    let article; // Assume this is defined elsewhere

    beforeEach(() => { 
        // Create a fresh mock config for EVERY test
        const freshSettingsConfig = { 
            openArticlesInCue: false // Default value
        };

        // Initialize spies with fresh instances
        httpService = jasmine.createSpyObj('Http', ['get', 'post']);
        alertService = jasmine.createSpyObj('AlertService', ['show']);
        stateService = jasmine.createSpyObj('StateService', ['getCurrentState']);
        // Attach the fresh config to the settings service spy
        settingsService = jasmine.createSpyObj('SettingsService', [], freshSettingsConfig);
        windowRefService = jasmine.createSpyObj('WindowRefService', ['getWindow']);

        TestBed.configureTestingModule({ 
            declarations: [ArticleFactoryComponent, ArticleComponent], 
            imports: [BrowserAnimationsModule], 
            providers: [ 
                {provide: Http, useValue: httpService}, 
                {provide: AlertService, useValue: alertService}, 
                {provide: StateService, useValue: stateService}, 
                {provide: WindowRefService, useValue: windowRefService}, 
                {provide: SettingsService, useValue: settingsService}, 
            ] 
        }); 

        // Initialize component with fresh mock state
        fixture = TestBed.createComponent(ArticleFactoryComponent); 
        comp = fixture.componentInstance; 
        comp.article = article; 
        fixture.detectChanges(); 

        de = fixture.debugElement.query(By.directive(ArticleComponent)); 
        el = de.nativeElement; 
        articleComponent = de.injector.get(ArticleComponent); 
    }); 

2. Fix Async Test Handling

Replace await de.triggerEventHandler with fixture.whenStable()—this waits for all async operations in Angular's Zone to finish before running your assertions:

it('should open an article in CS on double click', async () => { 
    const openArticleInCueSpy = spyOn(articleComponent, 'openArticleInCue').and.returnValue(undefined); 
    const openArticleInCsSpy = spyOn(articleComponent, 'openArticleInCs').and.returnValue(undefined); 

    // Trigger the double-click
    de.triggerEventHandler("dblclick", new MouseEvent("dblclick"));
    // Wait for all async logic in the component to complete
    await fixture.whenStable();
    fixture.detectChanges(); 

    expect(openArticleInCueSpy).not.toHaveBeenCalled(); 
    expect(openArticleInCsSpy).toHaveBeenCalled(); 
}); 

3. Update Mock State Before Component Initialization

For your second test, update the mock setting before initializing the component to ensure the component picks up the new value on startup:

it('should open an article in CUE on double click', async () => { 
    // Update the mock BEFORE creating the component
    settingsService.openArticlesInCue = true;
    
    // Re-initialize the component to use the updated mock
    fixture = TestBed.createComponent(ArticleFactoryComponent); 
    comp = fixture.componentInstance; 
    comp.article = article; 
    fixture.detectChanges(); 

    de = fixture.debugElement.query(By.directive(ArticleComponent)); 
    articleComponent = de.injector.get(ArticleComponent); 

    const openArticleInCueSpy = spyOn(articleComponent, 'openArticleInCue').and.returnValue(undefined); 
    const openArticleInCsSpy = spyOn(articleComponent, 'openArticleInCs').and.returnValue(undefined); 

    de.triggerEventHandler("dblclick", new MouseEvent("dblclick"));
    await fixture.whenStable();
    fixture.detectChanges(); 

    expect(openArticleInCueSpy).toHaveBeenCalled(); 
    expect(openArticleInCsSpy).not.toHaveBeenCalled(); 
});

4. Optional: Reset TestBed Between Tests

If you still see state leaks, add an afterEach hook to fully reset the TestBed:

afterEach(() => {
    TestBed.resetTestingModule();
});

Key Takeaways

  • Never share mutable mock state between tests—always create fresh mocks/configs in beforeEach.
  • Use fixture.whenStable() for async Angular tests instead of guessing with await on non-promise methods.
  • If you need to test a component with different mock states, update the mock before initializing the component to ensure it picks up the change.

内容的提问来源于stack exchange,提问作者Lukas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:44:08