Cypress Spy未触发:Angular组件Mock服务方法断言失败排查
问题:Angular组件测试中Mock服务方法调用断言失败
当选择无效文件类型时,EditProfileCardComponent会调用messageHandlingService.displayMessage方法,但Cypress测试中第二个断言失败,提示该方法从未被调用,第一个断言(验证test值更新)已通过。
组件TypeScript代码
export class EditProfileCardComponent { test = 'test' constructor(private messageHandlingService: MessageHandlingService) {} onFileSelected(event: any) { const file: File = event.target.files[0]; if(file.type != ValidFileTypes.JPG && file.type != ValidFileTypes.PNG) { this.test = "passed"; // this correctly sets the value to 'passed' this.displayInvalidFileTypeMessage(); } else { // Some other logic... } } private displayInvalidFileTypeMessage() { this.messageHandlingService.displayMessage({ message: "File selected must be JPEG or PNG file", action: SnackbarAction.Close, classType: SnackbarClassType.Error, duration: SnackbarDuration.Medium }); } }
HTML代码
<p data-cy="test">{{test}}</p> <input type="file" class="d-none" data-cy="file-input" accept=".png, .jpg" (change)="onFileSelected($event)" #fileUpload>
测试代码
const messageHandlingService = { displayMessage: (snackBarItem: SnackBarItem) => { return null; } } beforeEach(() => { cy.mount(EditProfileCardComponent, setupCypressConfig<EditProfileCardComponent>({ providers: [ {provide: MessageHandlingService, useValue: messageHandlingService} ] })); }); it("onFileSelected method should call display message method in messageHandlingService when invalid file type is selected", () => { cy.spy(messageHandlingService, 'displayMessage'); cy.get(elementBindings.fileInput).selectFile('cypress/fixtures/incorrect file type.webp', { force: true }); cy.wait(1000); //1st assertion (THIS PASSES) cy.get('[data-cy="test"]').should('have.text', 'passed'); //2nd assertion (THIS FAILS) expect(messageHandlingService.displayMessage).to.be.calledOnceWithExactly({ message: "File selected must be JPEG or PNG file", action: SnackbarAction.Close, classType: SnackbarClassType.Error, duration: SnackbarDuration.Medium }); });
错误信息
AssertionError: expected displayMessage to have been called exactly once with exact arguments Object{4}, but it was never called
实际MessageHandlingService代码
import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Subject, tap } from 'rxjs'; import { SnackBarItem } from '../models/snackbar-item'; @Injectable({ providedIn: 'root' }) export class MessageHandlingService { constructor(private snackbar: MatSnackBar) { } private displayNewMessage = new Subject<SnackBarItem>(); newMessageReceived$ = this.displayNewMessage.pipe( tap(snackBarItem => this.displayMessage(snackBarItem)) ).subscribe(); onDisplayNewMessage(snackBarItem: SnackBarItem) { this.displayNewMessage.next(snackBarItem); } displayMessage(snackBarItem: SnackBarItem) { this.snackbar.open(snackBarItem.message, snackBarItem.action, { panelClass: [snackBarItem.classType], duration: snackBarItem.duration, horizontalPosition: 'end' }); } }
原因分析
问题出在Spy创建时机:
组件在beforeEach中已经完成挂载,此时组件内部已经持有了messageHandlingService.displayMessage方法的原始引用。之后在测试用例中调用cy.spy只是修改了外部对象的方法,但组件仍然会调用初始化时拿到的原始方法,导致Spy无法捕获到调用。
解决方案
需要在挂载组件之前创建Spy,确保组件注入的是已经被Spy包装后的服务方法。修改测试代码如下:
beforeEach(() => { // 在挂载前创建带Spy的服务对象 const messageHandlingService = { displayMessage: cy.spy().as('displayMessage') // 创建命名Spy }; cy.mount(EditProfileCardComponent, setupCypressConfig<EditProfileCardComponent>({ providers: [ {provide: MessageHandlingService, useValue: messageHandlingService} ] })); }); it("onFileSelected method should call display message method in messageHandlingService when invalid file type is selected", () => { cy.get(elementBindings.fileInput).selectFile('cypress/fixtures/incorrect file type.webp', { force: true }); // 第一个断言 cy.get('[data-cy="test"]').should('have.text', 'passed'); // 第二个断言:使用命名Spy进行验证 cy.get('@displayMessage').should('have.been.calledOnceWithExactly', { message: "File selected must be JPEG or PNG file", action: SnackbarAction.Close, classType: SnackbarClassType.Error, duration: SnackbarDuration.Medium }); });
关键修改点:
- 将服务对象的创建和Spy定义移到
beforeEach内部,确保每次测试使用全新的Spy实例。 - 直接用
cy.spy()定义服务的displayMessage方法,组件挂载时注入的就是已被Spy包装后的方法。 - 使用Cypress命名Spy(
.as('displayMessage')),通过cy.get('@displayMessage')断言,符合Cypress异步测试逻辑,可移除手动cy.wait(1000)。
内容的提问来源于stack exchange,提问作者Pablo52
相关产品推荐
相关产品推荐

