Angular 2单元测试可观察错误(HTTP)

Jac*_*sor 6 unit-testing angular2-testing angular2-observables angular

我正在尝试为我的API服务编写单元测试,但在捕获HTTP错误时遇到一些问题.我遵循本指南以及Angular2文档,因为该指南在某些小范围内(稍微)过时了.

所有单元测试都与服务引发错误的单元测试分开(由于HTTP状态代码错误).我可以通过退出告诉我response.ok.从我所读到的,这与单元测试没有异步执行有关,因此,不等待错误响应.但是,我不知道为什么会出现这种情况,因为我async()beforeEach方法中使用了效用函数.

API服务

get(endpoint: string, authenticated: boolean = false): Observable<any> {
    endpoint = this.formatEndpoint(endpoint);
    return this.getHttp(authenticated) // Returns @angular/http or a wrapper for handling auth headers
        .get(endpoint)
        .map(res => this.extractData(res))
        .catch(err => this.handleError(err)); // Not in guide but should work as per docs
}
private extractData(res: Response): any {
    let body: any = res.json();
    return body || { };
}

private handleError(error: Response | any): Observable<any> {
    // TODO: Use a remote logging infrastructure
    // TODO: User error notifications
    let errMsg: string;
    if (error instanceof Response) {
        const body: any = error.json() || '';
        const err: string = body.error || JSON.stringify(body);
        errMsg = `${error.status} - ${error.statusText || ''}${err}`;
    } else {
        errMsg = error.message ? error.message : error.toString();
    }
    console.error(errMsg);
    return Observable.throw(errMsg);
}
Run Code Online (Sandbox Code Playgroud)

错误单元测试

// Imports

describe('Service: APIService', () => {
    let backend: MockBackend;
    let service: APIService;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            providers: [
                BaseRequestOptions,
                MockBackend,
                APIService,
                {
                    deps: [
                        MockBackend,
                        BaseRequestOptions
                    ],
                    provide: Http,
                        useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
                            return new Http(backend, defaultOptions);
                        }
                },
                {provide: AuthHttp,
                    useFactory: (http: Http, options: BaseRequestOptions) => {
                        return new AuthHttp(new AuthConfig({}), http, options);
                    },
                    deps: [Http, BaseRequestOptions]
                }
            ]
        });
        const testbed: any = getTestBed();
        backend = testbed.get(MockBackend);
        service = testbed.get(APIService);
    }));

    /**
     * Utility function to setup the mock connection with the required options
     * @param backend
     * @param options
     */
    function setupConnections(backend: MockBackend, options: any): any {
        backend.connections.subscribe((connection: MockConnection) => {
            const responseOptions: any = new ResponseOptions(options);
            const response: any = new Response(responseOptions);
            console.log(response.ok); // Will return false during the error unit test and true in others (if spyOn log is commented).
            connection.mockRespond(response);
        });
    }

    it('should log an error to the console on error', () => {
        setupConnections(backend, {
            body: { error: `Some strange error` },
            status: 400
        });
        spyOn(console, 'error');
        spyOn(console, 'log');

        service.get('/bad').subscribe(null, e => {
            // None of this code block is executed.
            expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
            console.log("Make sure an error has been thrown");
        });

        expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown."); // Fails
    });
Run Code Online (Sandbox Code Playgroud)

更新1

当我检查第一个回调时,response.ok未定义.这让我相信setupConnections公用事业有些不对劲.

    it('should log an error to the console on error', async(() => {
        setupConnections(backend, {
            body: { error: `Some strange error` },
            status: 400
        });
        spyOn(console, 'error');
        //spyOn(console, 'log');

        service.get('/bad').subscribe(res => {
            console.log(res); // Object{error: 'Some strange error'}
            console.log(res.ok); // undefined
        }, e => {
            expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
            console.log("Make sure an error has been thrown");
        });

        expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown.");
    }));
Run Code Online (Sandbox Code Playgroud)

更新2

如果不是在get方法中捕获错误而是在map中明确地执行它,那么仍然有相同的问题.

get(endpoint: string, authenticated: boolean = false): Observable<any> {
    endpoint = this.formatEndpoint(endpoint);
    return this.getHttp(authenticated).get(endpoint)
        .map(res => {
            if (res.ok) return this.extractData(res);
            return this.handleError(res);
        })
        .catch(this.handleError);
}
Run Code Online (Sandbox Code Playgroud)

更新3

经过一番讨论后提交了这个问题

Pau*_*tha 3

据我所知,这与单元测试不异步执行有关,因此不等待错误响应。但是,我不知道为什么会出现这种情况,因为我在方法中使用了async()实用函数beforeEach

您需要在测试用例( )中使用它it。所做async的是创建一个测试区域,等待所有异步任务完成后再完成测试(或测试区域,例如beforeEach)。

因此,在退出方法之前,它async只是beforeEach等待方法中的异步任务完成。但他们it也需要同样的东西。

it('should log an error to the console on error', async(() => {

}))
Run Code Online (Sandbox Code Playgroud)

更新

除了缺失之外async,似乎还有一个错误MockConnection。如果你看一下mockRespond,它总是调用next,而不考虑状态代码

mockRespond(res: Response) {
  if (this.readyState === ReadyState.Done || this.readyState === ReadyState.Cancelled) {
    throw new Error('Connection has already been resolved');
  }
  this.readyState = ReadyState.Done;
  this.response.next(res);
  this.response.complete();
}
Run Code Online (Sandbox Code Playgroud)

他们有一个mockError(Error)方法,这就是所谓的error

mockError(err?: Error) {
  // Matches ResourceLoader semantics
  this.readyState = ReadyState.Done;
  this.response.error(err);
}
Run Code Online (Sandbox Code Playgroud)

但这并不允许您传递Response. 这与真实的工作方式不一致XHRConnection,真实的工作方式检查状态,并Response通过nextor发送error,但是相同的Response

response.ok = isSuccess(status);
if (response.ok) {
  responseObserver.next(response);
  // TODO(gdi2290): defer complete if array buffer until done
  responseObserver.complete();
  return;
}
responseObserver.error(response);
Run Code Online (Sandbox Code Playgroud)

对我来说听起来像是一个错误。您可能应该报告一些事情。他们应该允许您发送Response或进行与 中mockError相同的检查。mockRespondXHRConnection

更新(由OP)SetupConnections()

目前的解决方案

function setupConnections(backend: MockBackend, options: any): any {
    backend.connections.subscribe((connection: MockConnection) => {
        const responseOptions: any = new ResponseOptions(options);
        const response: any = new Response(responseOptions);

        // Have to check the response status here and return the appropriate mock
        // See issue: https://github.com/angular/angular/issues/13690
        if (responseOptions.status >= 200 && responseOptions.status <= 299)
            connection.mockRespond(response);
        else
            connection.mockError(response);
    });
}
Run Code Online (Sandbox Code Playgroud)