在我的Jasmine测试规范中,有时我会监视authState
,我的模拟服务mockAngularFireAuth
的属性,并返回代表该特定测试下应用状态的不同值。
在一次测试中,这种方法完美无缺,断言是正确的;看到测试:
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');
}));
});
});
});
您可以看到我在哪里评论了间谍,而不得不劫持authSate
的mockAngularFireAuth
属性,以使断言成功,强行改变它的价值 - 我不应该做的事情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
,因此间谍没有返回我设置的相应值吗?
我认为这可能是因为Jasmine无法监视不属于函数的属性(不是我的知识),或者是getter / setter。
但是为什么间谍在
AuthService
catastrophically fails
通过?
答案 0 :(得分:0)
根据我的更新;你不能监视属性 - 只有函数(或方法)和属性getter和setter。
相反,我向setAuthState
添加了一个mockAngularFireAuth
方法,如果authState
属性可以改变该值。
它基本上与我完全一样,但没有违反TypeScript中的常量规则。由于它是一个模拟服务,我认为这个附加方法不存在重要。
然而,我并不完全确定成功规范的原因。我想这可能是因为方法throw
就是这样一个功能;因此它可能成为茉莉花间谍的回归价值。
以下是我改变测试的方式:
// 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();
}));
});