Angular 2服务单元测试异常:构造方法调用方法致测试误通过
问题根源:测试时序不匹配导致的异常
你遇到的问题核心是测试代码的执行顺序和构造函数的调用时机错位,导致你设置的Spy完全没在构造函数调用checkName()的时候生效。
我们拆解下当前测试的执行流程就能明白:
- 在
beforeEach中调用service1 = TestBed.get(Service1)时,Service1的构造函数会立即执行,进而触发this.checkName()。 - 此时你还没设置
spyOn(service2, 'getName'),所以service2.getName()执行的是原始逻辑(大概率返回undefined),直接触发了router.navigate(['bad-name-page'])。 - 之后你才设置Spy并再次调用
service1.checkName()——这一次getName确实返回'test',不会触发导航,但构造函数里的第一次调用已经让routerStub.navigate被调用过了,所以你的expect断言自然通过,完全偏离了你预期的测试场景。
针对构造函数调用方法的正确测试方式
我们需要调整测试时序,确保依赖的Mock/Spy在构造函数执行前就准备好,或者分开测试构造函数的行为和方法本身的逻辑。
1. 测试构造函数中调用checkName()的行为
如果你想验证构造函数触发checkName()的逻辑是否正确,需要在实例化Service1之前就配置好service2的Spy:
describe('Service: Service1', () => { let service1: Service1; let service2: Service2; let routerStub: any; beforeEach(() => { routerStub = { navigate: jasmine.createSpy('navigate') }; TestBed.configureTestingModule({ providers: [ Service1, {provide: Service2, useValue: Service2Stub}, {provide: Router, useValue: routerStub} ] }); // 先获取service2实例,提前设置Spy service2 = TestBed.get(Service2); spyOn(service2, 'getName').and.returnValue('test'); // 再获取service1,此时构造函数执行时Spy已经生效 service1 = TestBed.get(Service1); }); it('should NOT navigate when name exists in constructor', () => { // 构造函数调用checkName时,getName返回'test',导航不会被触发 expect(routerStub.navigate).not.toHaveBeenCalledWith(['bad-name-page']); }); it('should navigate when name is undefined in constructor', () => { // 重置Spy让getName返回undefined service2.getName.and.returnValue(undefined); // 重新实例化service1触发构造函数逻辑 service1 = TestBed.get(Service1); expect(routerStub.navigate).toHaveBeenCalledWith(['bad-name-page']); }); });
2. 单独测试checkName()方法的逻辑
如果你只想测试checkName()本身的逻辑,不想被构造函数的调用干扰,可以手动创建Service1实例,完全控制依赖和执行时机:
describe('Service: Service1', () => { let service1: Service1; let mockService2: jasmine.SpyObj<Service2>; let mockRouter: jasmine.SpyObj<Router>; beforeEach(() => { // 提前创建带Spy的Mock对象 mockService2 = jasmine.createSpyObj('Service2', ['getName']); mockRouter = jasmine.createSpyObj('Router', ['navigate']); // 手动实例化Service1,提前控制Mock返回值 mockService2.getName.and.returnValue('initial-test'); service1 = new Service1(mockService2, mockRouter); }); it('should NOT navigate when name is valid', () => { // 重置Spy返回值 mockService2.getName.and.returnValue('test'); // 调用checkName service1.checkName(); // 验证导航未触发 expect(mockRouter.navigate).not.toHaveBeenCalledWith(['bad-name-page']); }); it('should navigate when name is undefined', () => { mockService2.getName.and.returnValue(undefined); service1.checkName(); expect(mockRouter.navigate).toHaveBeenCalledWith(['bad-name-page']); }); });
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

