如何在测试中模拟 Angular 4.3 httpClient 的错误响应

Posted

技术标签:

【中文标题】如何在测试中模拟 Angular 4.3 httpClient 的错误响应【英文标题】:How to mock Angular 4.3 httpClient an error response in testing 【发布时间】:2018-02-12 04:55:08 【问题描述】:

我有一个下面的interceptor auth-interceptor.service.ts

import Injectable, Injector from '@angular/core';
import HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest from '@angular/common/http';
import Observable from 'rxjs/Observable';
import Cookie from './cookie.service';
import Router from '@angular/router';
import UserService from './user.service';
import ToasterService from '../toaster/toaster.service';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

@Injectable()
export class AuthInterceptor implements HttpInterceptor 
    constructor(private injector: Injector) 

    private handleError(err: HttpErrorResponse): Observable<any> 
        let errorMsg;
        if (err.error instanceof Error) 
            // A client-side or network error occurred. Handle it accordingly.
            errorMsg = `An error occurred: $err.error.message`;
         else 
            // The backend returned an unsuccessful response code.
            // The response body may contain clues as to what went wrong,
            errorMsg = `Backend returned code $err.status, body was: $err.error`;
        
        if (err.status === 401 || err.status === 403) 
            this.injector.get(UserService).purgeAuth();
            this.injector.get(ToasterService).showError(`Unauthorized`, errorMsg);
            this.injector.get(Router).navigateByUrl(`/login`);
        
        console.error(errorMsg);
        return Observable.throw(errorMsg);
    

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
        // Clone the request to add the new header.
        const authReq = req.clone(headers: req.headers.set(Cookie.tokenKey, Cookie.getToken()));
        // Pass on the cloned request instead of the original request.
        return next.handle(authReq).catch(err => this.handleError(err));
    

现在我正在尝试模拟 http.get 来抛出错误,以便方法 handleError 控制错误消息。

以下是我对测试用例的处理方法auth-interceptor.service.specs.ts

import async, inject, TestBed from '@angular/core/testing';

import AuthInterceptor from './auth-interceptor.service';
import ApiService from './api.service';
import HttpClientTestingModule, HttpTestingController from '@angular/common/http/testing';
import environment from '../../../environments/environment';

describe(`AuthInterceptor`, () => 
    const somePath = `/somePath`;

    beforeEach(() => 
        TestBed.configureTestingModule(
            imports: [HttpClientTestingModule],
            providers: [AuthInterceptor, ApiService]
        );
    );

    it(`should be created`, inject([AuthInterceptor], (service: AuthInterceptor) => 
        expect(service).toBeTruthy();
    ));


    it(`should log an error to the console on error on get()`, async(inject([ApiService, HttpTestingController],
        (apiService: ApiService, httpMock: HttpTestingController) => 
            spyOn(console, 'error');
            apiService.get(somePath).subscribe((res) => 
                console.log(`in success:`, res);
            , (error) => 
                console.log(`in error:`, error);
            );

            const req = httpMock.expectOne(`$environment.apiUri$somePath`);
            req.flush(
                type: 'ERROR',
                status: 404,
                body: JSON.stringify(color: `blue`)
            );
            expect(console.error).toHaveBeenCalled();
        ))
    );
);

在刷新响应时,我不确定如何刷新错误响应,以便在我的拦截器中调用方法 handleError 并最终调用 console.error。文档对我的情况没有任何示例。任何帮助或建议表示赞赏。

【问题讨论】:

您的req 对象是TestRequest 类的一个实例。它也有error() 方法。你试过req.error(new ErrorEvent('fail'), status: 404);之类的吗? 【参考方案1】:

HttpTestingController 类中的expectOne 方法返回一个TestRequest 对象。这个TestRequest 类有一个flush 方法,可以用来传递

成功和不成功的响应。

我们可以通过返回正文以及一些额外的响应标头(如果有)来解决请求。相关信息可以在here找到。

现在,回到你如何做到这一点。您可以根据自己的用例自定义以下代码 sn-p。

http = TestBed.get(HttpTestingController);
let response: any;
let errResponse: any;
const mockErrorResponse =  status: 400, statusText: 'Bad Request' ;
const data = 'Invalid request parameters';
apiService.get(somePath).subscribe(res => response = res, err => errResponse = err);
http.expectOne('url/being/monitored').flush(data, mockErrorResponse);
expect(errResponse).toBe(data);

注意:在撰写此评论时,mockErrorResponse 中需要 statusText。相关信息可以在here找到。

P.S.TestRequest 类的 error 方法可用于在我们的测试用例中模拟网络错误,因为它需要一个 Error 实例。下面的代码 sn -p 说明了这一点。

http.expectOne(someUrl).error(new ErrorEvent('network error'));

【讨论】:

如果检查错误,你不应该改为http.expectOne(someUrl).error(null, mockErrorResponse)吗?创建ErrorEvent 不允许您包含状态代码,因此对我而言,它似乎不如我建议的解决方案那么理想。 我正在为此苦苦挣扎。如果我有一个茉莉花单元测试这样做它可以工作。如果我有多个,它实际上是在抛出我试图模拟的错误并终止测试周期。 @Jimbo 我遇到了同样的问题。我的问题是我没有添加错误回调。 apiService.get(somePath).subscribe(res =&gt; response = res, err =&gt; errResponse = err); 第二个回调非常重要,它甚至可以是像(err) =&gt; 这样的空函数。

以上是关于如何在测试中模拟 Angular 4.3 httpClient 的错误响应的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Angular 组件中模拟服务功能以进行单元测试

如何在 Angular 单元测试中模拟/监视导入的函数

Angular 6 - 如何在单元测试中模拟 router.events url 响应

Angular如何测试服务方法?

在单元测试中使用参数模拟 ngrx 存储选择器(Angular)

出于测试目的,如何在 angular2 中模拟已激活的父路由?