目前您可以找到很多不同的方法来对您的角度应用进行单元测试。很多已经过时了,基本上目前还没有真正的文档。所以我真的不确定使用哪种方法。
目前似乎一个好的方法是使用TestComponentBuilder
,但是我在测试部分代码时遇到了一些麻烦,特别是如果我的组件上的函数使用了一个返回可观察的注入服务。
例如,具有身份验证服务的基本登录组件(对请求使用BackendService)。
我在这里省略了模板,因为我不想用UnitTests测试它们(据我所知,TestComponentBuilder
对此非常有用,但我只想对我的所有单元测试使用通用方法,似乎TestComponentBuilder
应该处理每个可测试的方面,请纠正我,如果我在这里错了)
所以我得到了LoginComponent
:
export class LoginComponent {
user:User;
isLoggingIn:boolean;
errorMessage:string;
username:string;
password:string;
constructor(private _authService:AuthService, private _router:Router) {
this._authService.isLoggedIn().subscribe(isLoggedIn => {
if(isLoggedIn) {
this._router.navigateByUrl('/anotherView');
}
});
}
login():any {
this.errorMessage = null;
this.isLoggingIn = true;
this._authService.login(this.username, this.password)
.subscribe(
user => {
this.user = user;
setTimeout(() => {
this._router.navigateByUrl('/anotherView');
}, 2000);
},
errorMessage => {
this.password = '';
this.errorMessage = errorMessage;
this.isLoggingIn = false;
}
);
}
}
我的AuthService
:
@Injectable()
export class AuthService {
private _user:User;
private _urls:any = {
...
};
constructor( private _backendService:BackendService,
@Inject(APP_CONFIG) private _config:Config,
private _localStorage:LocalstorageService,
private _router:Router) {
this._user = _localStorage.get(LOCALSTORAGE_KEYS.CURRENT_USER);
}
get user():User {
return this._user || this._localStorage.get(LOCALSTORAGE_KEYS.CURRENT_USER);
}
set user(user:User) {
this._user = user;
if (user) {
this._localStorage.set(LOCALSTORAGE_KEYS.CURRENT_USER, user);
} else {
this._localStorage.remove(LOCALSTORAGE_KEYS.CURRENT_USER);
}
}
isLoggedIn (): Observable<boolean> {
return this._backendService.get(this._config.apiUrl + this._urls.isLoggedIn)
.map(response => {
return !(!response || !response.IsUserAuthenticated);
});
}
login (username:string, password:string): Observable<User> {
let body = JSON.stringify({username, password});
return this._backendService.post(this._config.apiUrl + this._urls.login, body)
.map(() => {
this.user = new User(username);
return this.user;
});
}
logout ():Observable<any> {
return this._backendService.get(this._config.apiUrl + this._urls.logout)
.map(() => {
this.user = null;
this._router.navigateByUrl('/login');
return true;
});
}
}
最后我的BackendService
:
@Injectable()
export class BackendService {
_lastErrorCode:number;
private _errorCodes = {
...
};
constructor( private _http:Http, private _router:Router) {
}
post(url:string, body:any):Observable<any> {
let options = new RequestOptions();
this._lastErrorCode = 0;
return this._http.post(url, body, options)
.map((response:any) => {
...
return body.Data;
})
.catch(this._handleError);
}
...
private _handleError(error:any) {
...
let errMsg = error.message || 'Server error';
return Observable.throw(errMsg);
}
}
现在我想测试登录的基本逻辑,有一次它应该失败并且我期望一条错误消息(由BackendService
函数中的handleError
抛出)和另一个测试它应该登录并设置我的User
- 对象
这是我目前针对Login.component.spec
的方法:
已更新:已添加fakeAsync
,如Günters答案所示。
export function main() {
describe('Login', () => {
beforeEachProviders(() => [
ROUTER_FAKE_PROVIDERS
]);
it('should try and fail logging in',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
tcb.createAsync(TestComponent)
.then((fixture: any) => {
tick();
fixture.detectChanges();
let loginInstance = fixture.debugElement.children[0].componentInstance;
expect(loginInstance.errorMessage).toBeUndefined();
loginInstance.login();
tick();
fixture.detectChanges();
expect(loginInstance.isLoggingIn).toBe(true);
fixture.detectChanges();
expect(loginInstance.isLoggingIn).toBe(false);
expect(loginInstance.errorMessage.length).toBeGreaterThan(0);
});
})));
it('should log in',
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
tcb.createAsync(TestComponent)
.then((fixture: any) => {
tick();
fixture.detectChanges();
let loginInstance = fixture.debugElement.children[0].componentInstance;
loginInstance.username = 'abc';
loginInstance.password = '123';
loginInstance.login();
tick();
fixture.detectChanges();
expect(loginInstance.isLoggingIn).toBe(true);
expect(loginInstance.user).toEqual(jasmine.any(User));
});
})));
});
}
@Component({
selector: 'test-cmp',
template: `<my-login></my-login>`,
directives: [LoginComponent],
providers: [
HTTP_PROVIDERS,
provide(APP_CONFIG, {useValue: CONFIG}),
LocalstorageService,
BackendService,
AuthService,
BaseRequestOptions,
MockBackend,
provide(Http, {
useFactory: function(backend:ConnectionBackend, defaultOptions:BaseRequestOptions) {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
})
]
})
class TestComponent {
}
此测试存在几个问题。
ERROR: 'Unhandled Promise rejection:', 'Cannot read property 'length' of null'
我得到这个来测试`loginInstance.errorMessage.length Expected true to be false.
后的第一次测试中,login
Expected undefined to equal <jasmine.any(User)>.
在应该登录后的第二次测试中。任何提示如何解决这个问题?我在这里使用了错误的方法吗? 任何帮助都会非常感激(我很抱歉文本/代码的墙;))
答案 0 :(得分:1)
由于您无法知道实际调用this._authService.login(this.username, this.password).subscribe( ... )
的时间,因此您无法同步继续测试并假设已发生subscribe
回调。实际上它还没有发生,因为同步代码(你的测试)首先被执行到最后。
fakeAsync
,它可以更好地控制测试期间的异步执行(我自己也没有使用过它)。