求助:如何测试基于NgZone与Google Places的Angular指令?
解决Angular Google Places指令的单元测试问题
我完全懂你现在的困扰——这个指令在实际应用里跑的好好的,一跑ng test就踩坑,google is not defined和CORS报错确实让人头大。我来一步步帮你梳理解决思路:
为什么会出现这些问题?
你的指令依赖全局的google对象,但Karma测试环境并没有加载Google Maps脚本;就算你硬加载,还会碰到浏览器的CORS限制(就是你看到的第二个错误)。记住:单元测试的核心是测试你自己的代码逻辑,而不是第三方API的功能,所以我们要Mock掉Google的相关对象,而非调用真实API。
第一步:Mock全局的google对象
在测试文件开头,先定义一个模拟的全局google对象,模拟places.Autocomplete的行为、方法和事件监听:
// 在所有导入语句之后,定义全局Mock declare global { interface Window { google: any; } } // 初始化Mock的Google Maps对象 window.google = { maps: { places: { Autocomplete: jasmine.createSpy('Autocomplete').and.callFake(function(el: any) { // 模拟Autocomplete实例的事件监听方法 this.addListener = jasmine.createSpy('addListener').and.callFake((eventName: string, callback: Function) => { // 把回调函数存起来,后面手动触发 this.placeChangedCallback = callback; }); // 模拟getPlace方法,返回测试用的地点数据 this.getPlace = jasmine.createSpy('getPlace').and.returnValue({ name: '测试咖啡店', formatted_address: '测试路123号' }); return this; }) } } };
第二步:修改测试用例,验证指令核心逻辑
现在我们可以专注测试指令的核心逻辑:当Autocomplete触发place_changed事件时,指令是否正确调用ngZone.run并emitplaceChange事件。修改后的完整测试代码如下:
import { By } from '@angular/platform-browser'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { Component, DebugElement, NgZone } from '@angular/core'; import { AppGooglePlaceDirective } from './google-places.directive'; // Mock全局google对象 declare global { interface Window { google: any; } } window.google = { maps: { places: { Autocomplete: jasmine.createSpy('Autocomplete').and.callFake(function(el: any) { this.addListener = jasmine.createSpy('addListener').and.callFake((eventName: string, callback: Function) => { this.placeChangedCallback = callback; }); this.getPlace = jasmine.createSpy('getPlace').and.returnValue({ name: '测试咖啡店', formatted_address: '测试路123号' }); return this; }) } } }; // 用于测试的宿主组件 @Component({ template: `<input type="search" appGooglePlace (placeChange)="onPlaceChange($event)" name="test"/>` }) class TestGooglePlaceComponent { receivedPlace: any; onPlaceChange(place: any) { this.receivedPlace = place; } } describe('AppGooglePlaceDirective', () => { let component: TestGooglePlaceComponent; let fixture: ComponentFixture<TestGooglePlaceComponent>; let searchInput: DebugElement; let autocompleteInstance: any; beforeEach(() => { TestBed.configureTestingModule({ declarations: [TestGooglePlaceComponent, AppGooglePlaceDirective] // NgZone用TestBed提供的真实实例即可,不需要Mock }); fixture = TestBed.createComponent(TestGooglePlaceComponent); component = fixture.componentInstance; searchInput = fixture.debugElement.query(By.css('input[name=test]')); fixture.detectChanges(); // 获取Mock的Autocomplete实例 autocompleteInstance = window.google.maps.places.Autocomplete.calls.mostRecent().returnValue; }); it('应该能创建指令实例', () => { const directive = new AppGooglePlaceDirective(searchInput.nativeElement, TestBed.inject(NgZone)); expect(directive).toBeTruthy(); }); it('应该监听place_changed事件并正确emit placeChange', () => { // 手动触发place_changed事件的回调函数 autocompleteInstance.placeChangedCallback(); // 验证组件是否收到了正确的地点数据 expect(component.receivedPlace).toEqual({ name: '测试咖啡店', formatted_address: '测试路123号' }); // 验证getPlace方法被调用 expect(autocompleteInstance.getPlace).toHaveBeenCalled(); // 验证监听了正确的事件名称 expect(autocompleteInstance.addListener).toHaveBeenCalledWith('place_changed', jasmine.any(Function)); }); });
关于能否调用真实Google API测试?
不建议这么做,原因如下:
- CORS限制:Google Maps API不允许从Karma的默认端口
localhost:9876发起跨域请求,会直接报错。 - 测试稳定性:依赖网络的测试容易不稳定,而且每次调用真实API也不高效。
- 单元测试职责:Google已经自己测试过他们的API功能了,我们的单元测试只需要验证:当Google的Autocomplete触发事件时,我们的指令是否正确处理并传递数据。
如果确实需要测试和真实API的集成,可以写E2E测试(比如用Cypress),E2E测试是在浏览器环境中运行,和你的应用环境一致,不会有CORS问题。
内容的提问来源于stack exchange,提问作者WizardPC
相关产品推荐
相关产品推荐

