Jasmine Spy 没有返回正确的值
Jasmine Spy not Returning Correct Value
在我的 Jasmine 测试规范中,有时我会监视 authState
,我的模拟服务的 属性 mockAngularFireAuth
和 return 代表状态的不同值该特定测试下的应用程序。
在一次测试中,这完美地工作并且断言是正确的;看测试:
AuthService
catastrophically fails
然而,当我在测试中以完全相同的方式监视 authState
时(例如)...
AuthService
can’t authenticate anonymously
AuthService.currentUid
should return undefined
…断言expect(service.currentUid).toBeUndefined()
失败。
currentUid
保留为最初设置的字符串 ("17WvU2Vj58SnTz8v7EqyYYb0WRc2")。
这是我的测试规格的精简版(这仅包括有问题的测试规格):
import { async, inject, TestBed } from '@angular/core/testing';
import { AngularFireAuth } from 'angularfire2/auth';
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { MockUser} from './mock-user';
import { environment } from '../environments/environment';
// An anonymous user
const authState: MockUser = {
displayName: null,
isAnonymous: true,
uid: '17WvU2Vj58SnTz8v7EqyYYb0WRc2'
};
// Mock AngularFireAuth
const mockAngularFireAuth: any = {
auth: jasmine.createSpyObj('auth', {
'signInAnonymously': Promise.resolve(authState)
}),
authState: Observable.of(authState)
};
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: AngularFireAuth, useValue: mockAngularFireAuth },
{ provide: AuthService, useClass: AuthService }
]
});
});
…
describe('can’t authenticate anonymously', () => {
…
describe('AuthService.currentUid', () => {
beforeEach(() => {
// const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
//
// spy.and.returnValue(Observable.of(null));
//
mockAngularFireAuth.authState = Observable.of(null);
});
it('should return undefined',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBeUndefined();
}));
});
});
describe('catastrophically fails', () => {
beforeEach(() => {
const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
spy.and.returnValue(Observable.throw(new Error('Some catastrophe')));
});
describe('AngularFireAuth.authState', () => {
it('should invoke it’s onError function', () => {
mockAngularFireAuth.authState.subscribe(null,
(error: Error) => {
expect(error).toEqual(new Error('Some catastrophe'));
});
});
});
describe('AuthService.currentUid', () => {
beforeEach(() => {
mockAngularFireAuth.authState = Observable.of(null);
});
it('should return undefined',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBeUndefined();
}));
});
});
describe('is authenticated anonymously already', () => {
beforeEach(() => {
// const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
//
// spy.and.returnValue(Observable.of(authState));
//
mockAngularFireAuth.authState = Observable.of(authState);
});
describe('.authSate.isAnonymous', () => {
it('should be true', async(() => {
mockAngularFireAuth.authState.subscribe((data: MockUser) => {
expect(data.isAnonymous).toBeTruthy();
});
}));
});
describe('AuthService.currentUid', () => {
it('should return "17WvU2Vj58SnTz8v7EqyYYb0WRc2"',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBe('17WvU2Vj58SnTz8v7EqyYYb0WRc2');
}));
});
});
describe('is authenticated with Facebook already', () => {
beforeEach(() => {
const obj: MockUser = authState;
// const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
//
// spy.and.returnValue(Observable.of(Object.assign(obj, {
// isAnonymous: false,
// uid: 'ZzVRkeduEW1bJC6pmcmb9VjyeERt'
// })));
//
mockAngularFireAuth.authState = Observable.of(Object.assign(obj, {
isAnonymous: false,
uid: 'ZzVRkeduEW1bJC6pmcmb9VjyeERt'
}));
});
describe('.authSate.isAnonymous', () => {
it('should be false', () => {
mockAngularFireAuth.authState.subscribe((data: MockUser) => {
expect(data.isAnonymous).toBe(false);
});
});
});
describe('AuthService.currentUid', () => {
it('should return "ZzVRkeduEW1bJC6pmcmb9VjyeERt"',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBe('ZzVRkeduEW1bJC6pmcmb9VjyeERt');
}));
});
});
});
你可以看到我在哪里注释掉了间谍,而是不得不劫持 mockAngularFireAuth
的 authSate
属性 到让断言成功,通过强行改变它的价值——这是我不应该做的,因为 mockAngularFireAuth
是一个常数。
为了完整性,这里是(部分)被测服务:
import { Injectable } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import 'rxjs/add/observable/of';
// import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class AuthService {
private authState: firebase.User;
constructor(private afAuth: AngularFireAuth) { this.init(); }
private init (): void {
this.afAuth.authState.subscribe((authState: firebase.User) => {
if (authState === null) {
this.afAuth.auth.signInAnonymously()
.then((authState: firebase.User) => {
this.authState = authState;
})
.catch((error: Error) => {
console.error(error);
});
} else {
this.authState = authState;
}
}, (error: Error) => {
console.error(error);
});
}
public get currentUid(): string {
return this.authState ? this.authState.uid : undefined;
}
}
是否因为在断言失败的规范中我没有订阅 authState
因此间谍没有 return 我设置的相应值?
更新:
我认为这可能是因为 Jasmine 无法监视不是函数的属性(据我所知),或者 getters/setters。
但是为什么间谍会在
AuthService
catastrophically fails
通过?
根据我的更新;你不能监视属性——只能监视函数(或方法)和 属性 getter 和 setter。
相反,我向 mockAngularFireAuth
添加了一个 setAuthState
方法,如果 authState
属性.
可以更改值
它基本上和我做的一模一样,但没有违反 TypeScript 中常量的规则。由于它是一项模拟服务,因此我认为存在此附加方法并不重要。
但是,我不完全确定为什么成功的规范如此。我认为这可能是因为方法 throw
本身就是一个函数;因此它可以成为 Jasmine 间谍的 return 值。
以下是我更改测试的方式:
// Mock AngularFireAuth
const mockAngularFireAuth: any = {
auth: jasmine.createSpyObj('auth', {
'signInAnonymously': Promise.resolve(authState)
}),
authState: Observable.of(authState),
setAuthState: (authState: MockUser): void => {
mockAngularFireAuth.authState = Observable.of(authState);
}
};
注意 setAuthState
。
这就是我更改规格的方式(代表性示例):
describe('AuthService.currentUid', () => {
beforeEach(() => {
mockAngularFireAuth.setAuthState(null);
});
it('should return undefined',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBeUndefined();
}));
});
在我的 Jasmine 测试规范中,有时我会监视 authState
,我的模拟服务的 属性 mockAngularFireAuth
和 return 代表状态的不同值该特定测试下的应用程序。
在一次测试中,这完美地工作并且断言是正确的;看测试:
AuthService
catastrophically fails
然而,当我在测试中以完全相同的方式监视 authState
时(例如)...
AuthService
can’t authenticate anonymously
AuthService.currentUid
should return undefined
…断言expect(service.currentUid).toBeUndefined()
失败。
currentUid
保留为最初设置的字符串 ("17WvU2Vj58SnTz8v7EqyYYb0WRc2")。
这是我的测试规格的精简版(这仅包括有问题的测试规格):
import { async, inject, TestBed } from '@angular/core/testing';
import { AngularFireAuth } from 'angularfire2/auth';
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { MockUser} from './mock-user';
import { environment } from '../environments/environment';
// An anonymous user
const authState: MockUser = {
displayName: null,
isAnonymous: true,
uid: '17WvU2Vj58SnTz8v7EqyYYb0WRc2'
};
// Mock AngularFireAuth
const mockAngularFireAuth: any = {
auth: jasmine.createSpyObj('auth', {
'signInAnonymously': Promise.resolve(authState)
}),
authState: Observable.of(authState)
};
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: AngularFireAuth, useValue: mockAngularFireAuth },
{ provide: AuthService, useClass: AuthService }
]
});
});
…
describe('can’t authenticate anonymously', () => {
…
describe('AuthService.currentUid', () => {
beforeEach(() => {
// const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
//
// spy.and.returnValue(Observable.of(null));
//
mockAngularFireAuth.authState = Observable.of(null);
});
it('should return undefined',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBeUndefined();
}));
});
});
describe('catastrophically fails', () => {
beforeEach(() => {
const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
spy.and.returnValue(Observable.throw(new Error('Some catastrophe')));
});
describe('AngularFireAuth.authState', () => {
it('should invoke it’s onError function', () => {
mockAngularFireAuth.authState.subscribe(null,
(error: Error) => {
expect(error).toEqual(new Error('Some catastrophe'));
});
});
});
describe('AuthService.currentUid', () => {
beforeEach(() => {
mockAngularFireAuth.authState = Observable.of(null);
});
it('should return undefined',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBeUndefined();
}));
});
});
describe('is authenticated anonymously already', () => {
beforeEach(() => {
// const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
//
// spy.and.returnValue(Observable.of(authState));
//
mockAngularFireAuth.authState = Observable.of(authState);
});
describe('.authSate.isAnonymous', () => {
it('should be true', async(() => {
mockAngularFireAuth.authState.subscribe((data: MockUser) => {
expect(data.isAnonymous).toBeTruthy();
});
}));
});
describe('AuthService.currentUid', () => {
it('should return "17WvU2Vj58SnTz8v7EqyYYb0WRc2"',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBe('17WvU2Vj58SnTz8v7EqyYYb0WRc2');
}));
});
});
describe('is authenticated with Facebook already', () => {
beforeEach(() => {
const obj: MockUser = authState;
// const spy: jasmine.Spy = spyOn(mockAngularFireAuth, 'authState');
//
// spy.and.returnValue(Observable.of(Object.assign(obj, {
// isAnonymous: false,
// uid: 'ZzVRkeduEW1bJC6pmcmb9VjyeERt'
// })));
//
mockAngularFireAuth.authState = Observable.of(Object.assign(obj, {
isAnonymous: false,
uid: 'ZzVRkeduEW1bJC6pmcmb9VjyeERt'
}));
});
describe('.authSate.isAnonymous', () => {
it('should be false', () => {
mockAngularFireAuth.authState.subscribe((data: MockUser) => {
expect(data.isAnonymous).toBe(false);
});
});
});
describe('AuthService.currentUid', () => {
it('should return "ZzVRkeduEW1bJC6pmcmb9VjyeERt"',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBe('ZzVRkeduEW1bJC6pmcmb9VjyeERt');
}));
});
});
});
你可以看到我在哪里注释掉了间谍,而是不得不劫持 mockAngularFireAuth
的 authSate
属性 到让断言成功,通过强行改变它的价值——这是我不应该做的,因为 mockAngularFireAuth
是一个常数。
为了完整性,这里是(部分)被测服务:
import { Injectable } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import 'rxjs/add/observable/of';
// import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class AuthService {
private authState: firebase.User;
constructor(private afAuth: AngularFireAuth) { this.init(); }
private init (): void {
this.afAuth.authState.subscribe((authState: firebase.User) => {
if (authState === null) {
this.afAuth.auth.signInAnonymously()
.then((authState: firebase.User) => {
this.authState = authState;
})
.catch((error: Error) => {
console.error(error);
});
} else {
this.authState = authState;
}
}, (error: Error) => {
console.error(error);
});
}
public get currentUid(): string {
return this.authState ? this.authState.uid : undefined;
}
}
是否因为在断言失败的规范中我没有订阅 authState
因此间谍没有 return 我设置的相应值?
更新:
我认为这可能是因为 Jasmine 无法监视不是函数的属性(据我所知),或者 getters/setters。
但是为什么间谍会在
AuthService
catastrophically fails
通过?
根据我的更新;你不能监视属性——只能监视函数(或方法)和 属性 getter 和 setter。
相反,我向 mockAngularFireAuth
添加了一个 setAuthState
方法,如果 authState
属性.
它基本上和我做的一模一样,但没有违反 TypeScript 中常量的规则。由于它是一项模拟服务,因此我认为存在此附加方法并不重要。
但是,我不完全确定为什么成功的规范如此。我认为这可能是因为方法 throw
本身就是一个函数;因此它可以成为 Jasmine 间谍的 return 值。
以下是我更改测试的方式:
// Mock AngularFireAuth
const mockAngularFireAuth: any = {
auth: jasmine.createSpyObj('auth', {
'signInAnonymously': Promise.resolve(authState)
}),
authState: Observable.of(authState),
setAuthState: (authState: MockUser): void => {
mockAngularFireAuth.authState = Observable.of(authState);
}
};
注意 setAuthState
。
这就是我更改规格的方式(代表性示例):
describe('AuthService.currentUid', () => {
beforeEach(() => {
mockAngularFireAuth.setAuthState(null);
});
it('should return undefined',
inject([ AuthService ], (service: AuthService) => {
expect(service.currentUid).toBeUndefined();
}));
});