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

Angular中Jasmine如何Spy对象内方法?测试报错解决

问题:测试backButtonHandler时出现"Expected a spy, but got Function."错误

我在CapacitorManager服务中实现了backButtonHandler方法,逻辑是当dataService.getItem返回的activeModalInstance(NgbModal实例)存在时调用其close方法,否则调用location.back()。我已经mock了dataService.getItem让它返回带close方法的对象,但测试时抛出"Expected a spy, but got Function."错误,怎么才能通过这个测试?

服务代码

backButtonHandler() {
    return () => {
        const activeModalInstance = this.dataService.getItem(Constants.activeModalInstanceKey);
        const doesActiveModalExist = activeModalInstance !== -1;
        if (doesActiveModalExist) {
            activeModalInstance.close();
        }
        else {
            this.location.back();
        }
    }
}

原测试代码

fdescribe('CapacitorManager', () => {
    let service: CapacitorManager;
    let dataService: DataService;
    let location: Location;
    let dataServiceStub: Partial<DataService>;
    beforeEach(async(() => {
        dataServiceStub = {
            getItem: () => {
                return {
                    close: () => {

                    }
                }
            }
        }
        TestBed.configureTestingModule({
            providers: [
                { provide: Location, useClass: SpyLocation },
                { provide: DataService, useValue: dataServiceStub },
                CapacitorManager
            ],
            imports: [
                RouterTestingModule
            ]
        }).compileComponents().then(() => {
            service = TestBed.inject(CapacitorManager);
            dataService = TestBed.inject(DataService);
            location = TestBed.inject(Location);
        });
    }));

    afterEach(() => {
        service = null;
    });

    it('should create the component', () => {
        expect(service).toBeTruthy();
    });

    it('should backButtonHandler call activeModal in case activeModalInstance exists', () => {
        const activeModalInstance = dataService.getItem("activeModal");
        spyOn(activeModalInstance, "close");            
        service.backButtonHandler();
        expect(activeModalInstance.close).toHaveBeenCalled();
        expect(location.back).not.toHaveBeenCalled();
    });

});

解决方法

问题根源

测试中存在两个核心问题:

  • 你的dataService stub每次调用getItem都会返回一个新的对象,导致你提前创建spy的实例和backButtonHandler内部调用getItem拿到的实例不是同一个,断言时自然找不到spy。
  • backButtonHandler返回的是一个函数,但测试里只调用了backButtonHandler(),没有执行这个返回的函数,内部逻辑根本没跑。

修复后的测试代码

fdescribe('CapacitorManager', () => {
    let service: CapacitorManager;
    let dataService: DataService;
    let location: SpyLocation;
    let mockModalInstance: { close: jasmine.Spy };

    beforeEach(async(() => {
        // 提前创建固定的mock实例,并给close方法创建spy
        mockModalInstance = {
            close: jasmine.createSpy('close')
        };

        const dataServiceStub: Partial<DataService> = {
            // 让getItem始终返回同一个mock实例
            getItem: jasmine.createSpy('getItem').and.returnValue(mockModalInstance)
        };

        TestBed.configureTestingModule({
            providers: [
                { provide: Location, useClass: SpyLocation },
                { provide: DataService, useValue: dataServiceStub },
                CapacitorManager
            ],
            imports: [
                RouterTestingModule
            ]
        }).compileComponents().then(() => {
            service = TestBed.inject(CapacitorManager);
            dataService = TestBed.inject(DataService);
            location = TestBed.inject(Location) as SpyLocation;
        });
    }));

    afterEach(() => {
        service = null;
    });

    it('should create the service', () => {
        expect(service).toBeTruthy();
    });

    it('should call activeModal.close() when activeModalInstance exists', () => {
        // 获取backButtonHandler返回的函数并执行它
        const handler = service.backButtonHandler();
        handler();

        // 断言close方法被调用
        expect(mockModalInstance.close).toHaveBeenCalled();
        // 断言location.back没有被调用
        expect(location.back).not.toHaveBeenCalled();
        // 可选:断言dataService.getItem被正确调用
        expect(dataService.getItem).toHaveBeenCalledWith(Constants.activeModalInstanceKey);
    });

});

额外注意事项

  • 确保测试中调用getItem的参数和服务里的Constants.activeModalInstanceKey一致,避免因为key不匹配导致逻辑走else分支。
  • 如果需要测试else分支(即没有modal实例时调用location.back),可以修改dataService stub的返回值为-1,然后断言location.back被调用。

内容的提问来源于stack exchange,提问作者Timuçin Çiçek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 03:45:42