【发布时间】:2016-06-07 09:23:50
【问题描述】:
目前您可以找到许多不同的方法来对您的 Angular 应用程序进行单元测试。很多已经过时了,基本上目前还没有真正的文档。所以我真的不确定使用哪种方法。
目前似乎一个好方法是使用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-object
这是我目前对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)>.在第二次测试后应该已经登录了。
任何提示如何解决这个问题?我在这里使用错误的方法吗? 任何帮助将不胜感激(我很抱歉文字/代码墙;))
【问题讨论】:
标签: unit-testing testing angular jasmine karma-runner