Angular指令手动正常但Karma Jasmine单元测试失败求助
PopupTargetDirective单元测试失败问题排查
问题描述
我正在为一款点击时触发弹窗的PopupTargetDirective编写单元测试。该指令手动测试完全正常,但Karma Jasmine单元测试却显示失败,我对此无法理解。
测试代码
@Component({ template: '<button class="btn" PopupTarget target="my_modal">open modal</button>' + '<popup popupId="my_modal">hello</popup>' }) export class TesterComponent { } describe('PopupTargetDirective', () => { let fixture: ComponentFixture<TesterComponent> let directive: PopupTargetDirective; let button: HTMLButtonElement; let popup: DebugElement beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [PopupTargetDirective, TesterComponent, PopupComponent] }) .compileComponents(); fixture = TestBed.createComponent(TesterComponent); button = fixture.debugElement.nativeElement.querySelector(".btn") directive = new PopupTargetDirective(new ElementRef<any>(button)); popup = fixture.debugElement.query(By.directive(PopupComponent)).nativeElement }); it('should create an instance', () => { expect(directive).toBeTruthy(); }) it('should show popup when clicked', () => { let dialog: HTMLDialogElement | null = fixture.debugElement.nativeElement.querySelector("dialog") console.log(button) expect(dialog?.open).toBeFalse() button.click() fixture.whenStable().then(() => { expect(dialog?.open).toBeTrue() }) }) });
指令代码
@Directive({ selector: '[PopupTarget]' }) export class PopupTargetDirective { @Input() target : string = ''; constructor(private el: ElementRef) { el.nativeElement.addEventListener("click", ()=>{ let targetPopup: HTMLDialogElement | null = document.getElementById(this.target) as HTMLDialogElement; targetPopup?.showModal() }) } }
弹窗HTML
<dialog #dialog id="{{popupId}}" class="modal tw-rounded-md" (close)="this.onClose.emit()" (cancel)="cancel($event)" > <form method="dialog" class="modal-box"> <div class="tw-flex tw-flex-col tw-justify-center"> <button #closeButton class="tw-self-end" *ngIf="showCloseButton">✕</button> <ng-content class="tw-self-auto"></ng-content> </div> </form> </dialog>
弹窗TS代码
@Component({ selector: 'popup', templateUrl: './popup.component.html' }) export class PopupComponent { @Input() showCloseButton: boolean = false; @Input({required: true}) popupId: string | undefined; @Output() onClose: EventEmitter<void> = new EventEmitter() @ViewChild("dialog") dialog: ElementRef<HTMLDialogElement> | undefined @ViewChild("closeButton") closeButton: ElementRef<HTMLButtonElement> | undefined cancel(event: Event) { if (!this.showCloseButton) event.preventDefault() else this.onClose.emit() } }
请问我的测试代码是否存在问题?如前所述,该指令手动测试完全正常,问题大概率出在测试代码上。
测试代码的问题分析
你的测试代码存在三个核心问题:
手动实例化指令导致输入属性未绑定
你通过new PopupTargetDirective(new ElementRef<any>(button))手动创建指令实例,但Angular并未参与这个过程,因此@Input() target属性不会被赋值为模板中的"my_modal"。点击按钮时,指令里的this.target是空字符串,自然找不到对应的弹窗元素。未触发变更检测初始化视图
创建组件后没有调用fixture.detectChanges(),Angular不会渲染模板、绑定输入属性,也不会初始化指令和组件的生命周期钩子,导致弹窗的id没有被正确渲染,指令也没有被正确初始化。异步测试未正确处理
测试中使用fixture.whenStable().then()但没有告知Jasmine等待异步操作完成,测试会直接跳过断言部分就结束,导致断言永远不会执行或执行时测试已经结束。
修正后的测试代码
@Component({ template: '<button class="btn" PopupTarget target="my_modal">open modal</button>' + '<popup popupId="my_modal">hello</popup>' }) export class TesterComponent { } describe('PopupTargetDirective', () => { let fixture: ComponentFixture<TesterComponent>; let directive: PopupTargetDirective; let buttonDebugEl: DebugElement; let button: HTMLButtonElement; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [PopupTargetDirective, TesterComponent, PopupComponent] }).compileComponents(); fixture = TestBed.createComponent(TesterComponent); // 获取按钮的DebugElement,用于获取指令实例 buttonDebugEl = fixture.debugElement.query(By.css(".btn")); button = buttonDebugEl.nativeElement; // 从DebugElement中获取Angular管理的指令实例,而非手动创建 directive = buttonDebugEl.injector.get(PopupTargetDirective); // 触发变更检测,初始化视图和绑定 fixture.detectChanges(); }); it('should create an instance', () => { expect(directive).toBeTruthy(); }); // 使用async/await处理异步操作 it('should show popup when clicked', async () => { const dialog: HTMLDialogElement | null = fixture.debugElement.nativeElement.querySelector("dialog"); // 初始状态弹窗未打开 expect(dialog?.open).toBeFalse(); button.click(); // 等待异步操作稳定 await fixture.whenStable(); // 再次触发变更检测,更新视图状态 fixture.detectChanges(); expect(dialog?.open).toBeTrue(); }); });
内容的提问来源于stack exchange,提问作者Heila Al-Mogren
相关产品推荐
相关产品推荐

