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); }); });
额外注意事项
- 原测试代码中
declarations数组遗漏了MainComponent,这是被测组件,必须声明才能让TestBed创建其实例。 - Mock组件的模板必须保留真实组件中与
ChildComponent的事件绑定逻辑,否则ChildComponent的EventEmitter触发后无法关联到Mock的方法。
内容的提问来源于stack exchange,提问作者Malso86
相关产品推荐
相关产品推荐

