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

Angular Jasmine测试:子组件向父组件事件发射Mock测试失败

问题分析与解决方案

你的测试失败核心原因是:组件不能通过providers数组来Mock。你在declarations里声明了真实的ParentComponent,Angular会渲染这个真实组件并执行它的默认逻辑,而你在providers中定义的parentComponentSpyObj完全没被用到,自然会出现调用次数为0的错误。

下面是两种可行的修复方案:


方案一:创建Mock组件替换真实组件

定义一个和真实ParentComponent选择器一致的Mock组件,保留必要的事件绑定逻辑,然后在测试模块中用它替换真实组件:

// 定义Mock Parent组件,匹配真实组件的选择器
@Component({
  selector: 'app-parent',
  template: '<app-child (event)="onEventReceived($event)"></app-child>'
})
class MockParentComponent {
  onEventReceived = jasmine.createSpy('onEventReceived');
}

describe('main component Spec', () => {
    let fixture: ComponentFixture<MainComponent>;
    let mainComponent: MainComponent;
    let mockParentComponent: MockParentComponent;

    beforeEach(waitForAsync(() => {
        TestBed.configureTestingModule({
            declarations: [
                MainComponent, // 必须声明被测组件
                MockParentComponent, // 用Mock替换真实ParentComponent
                ChildComponent
            ],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],
            imports: [FlexLayoutModule]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(MainComponent);
        mainComponent = fixture.componentInstance;
        // 获取Mock组件实例
        mockParentComponent = fixture.debugElement.query(By.css('app-parent')).componentInstance;
        fixture.detectChanges();
    });

    it(`Should call onEventReceived of ParentComponent when child button is pressed`, () => {
        // Given
        const childComponentButton = fixture.debugElement.nativeElement.querySelector('#button');

        // When
        childComponentButton.click();
        fixture.detectChanges();

        // Then
        expect(mockParentComponent.onEventReceived).toHaveBeenCalledTimes(1);
    });
});

方案二:用TestBed.overrideComponent替换组件实现

如果不想单独定义Mock类,可以直接通过overrideComponent覆盖真实ParentComponent的逻辑:

describe('main component Spec', () => {
    let fixture: ComponentFixture<MainComponent>;
    let mainComponent: MainComponent;
    let parentSpy: jasmine.SpyObj<ParentComponent>;

    beforeEach(waitForAsync(() => {
        parentSpy = jasmine.createSpyObj('ParentComponent', ['onEventReceived']);

        TestBed.configureTestingModule({
            declarations: [
                MainComponent,
                ParentComponent,
                ChildComponent
            ],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],
            imports: [FlexLayoutModule]
        })
        // 覆盖ParentComponent的实现
        .overrideComponent(ParentComponent, {
            set: {
                template: '<app-child (event)="onEventReceived($event)"></app-child>',
                componentClass: class extends ParentComponent {
                    onEventReceived = parentSpy.onEventReceived;
                }
            }
        })
        .compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(MainComponent);
        mainComponent = fixture.componentInstance;
        fixture.detectChanges();
    });

    it(`Should call onEventReceived of ParentComponent when child button is pressed`, () => {
        // Given
        const childComponentButton = fixture.debugElement.nativeElement.querySelector('#button');

        // When
        childComponentButton.click();
        fixture.detectChanges();

        // Then
        expect(parentSpy.onEventReceived).toHaveBeenCalledTimes(1);
    });
});

额外注意事项

  1. 原测试代码中declarations数组遗漏了MainComponent,这是被测组件,必须声明才能让TestBed创建其实例。
  2. Mock组件的模板必须保留真实组件中与ChildComponent的事件绑定逻辑,否则ChildComponent的EventEmitter触发后无法关联到Mock的方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 01:57:48