6

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);
}

エラー単体テスト

// 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
    });

更新 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.");
    }));

更新 2

get メソッドでエラーをキャッチする代わりに、マップで明示的にエラーをキャッチした場合でも、同じ問題が発生します。

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);
}

アップデート 3

いくつかの議論の後、この問題が提出されました

4

2 に答える 2

3

私が読んだことから、これはユニットテストが非同期に実行されないことに関係しているため、エラー応答を待っていません。ただし、メソッドasync()でユーティリティ関数を使用したため、なぜこれが当てはまるのかわかりませんbeforeEach

テスト ケース ( ) で使用する必要がありますitasyncテストを完了する前に、すべての非同期タスクが完了するのを待つテスト ゾーンを作成します (またはテスト領域、たとえば) beforeEach

そのため、 は非同期タスクがメソッドで完了するasyncbeforeEachを待ってから終了します。しかし、it同じことが必要です。

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

}))

アップデート

の欠落は別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();
}

彼らにはmockError(Error)メソッドがあり、それが呼び出しますerror

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

しかし、これはあなたがaを渡すことを許可しませんResponseXHRConnectionこれは、ステータスをチェックし、またはResponseを介し​​ていずれかを送信する実際の動作と矛盾していますが、同じです。nexterrorResponse

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);

私にはバグのように聞こえます。おそらく報告する必要があるもの。で を送信するか、 で行うのと同じチェックを で実行できるようにする必要ResponsemockErrorありmockRespondますXHRConnection

更新 (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);
    });
}
于 2016-12-28T15:38:52.953 に答える