模拟 Observable 以在 Jest 中抛出错误

Posted

技术标签:

【中文标题】模拟 Observable 以在 Jest 中抛出错误【英文标题】:Mocking Observable to throw error in Jest 【发布时间】:2020-09-27 22:05:10 【问题描述】:

我正在尝试模拟 Angular 的 HttpClientPUT 调用以引发错误。我正在使用throwError。它不工作。我应该改变什么让它抛出错误并调用handleError 方法? 我正在使用 Jest

it(`should call the 'handleError' method when a request to store data was not successful`, () => 
    const error: HttpErrorResponse = 
      status: 401,
      message: 'You are not logged in',
     as HttpErrorResponse;

    jest.spyOn(httpClientServiceMock, 'put').mockReturnValue(throwError(error));
    const spy = jest.spyOn(httpService, 'handleError');

    httpService.requestCall('some-url', ApiMethod.PUT, );
    expect(spy).toBeCalled();
  );

服务文件

  requestCall(url: string, method: ApiMethod, data?: any): Observable<any> 
    const headers = 
      'X-XSRF-TOKEN': this.xsrfToken,
      'Content-Type': 'application/json; charset=UTF-8',
    ;

    const requestConfig = 
      withCredentials: true,
      headers,
    ;

    switch (method) 
      case ApiMethod.GET:
        return this._http.get(url,  withCredentials: true );
      case ApiMethod.PUT:
        return this._http
          .put(url, data, requestConfig)
          .pipe(catchError((error) => this.handleError(error)));
    
  

  handleError(error: HttpErrorResponse): any 
    if (error.error instanceof ErrorEvent) 
      console.error(`An error occurred: $error.error.message`);
    

    return throwError( error: error.message, status: error.status );
  

【问题讨论】:

【参考方案1】:

你已经很接近了!

你必须从httpService.requestCall('some-url', ApiMethod.PUT, )函数返回subscribe to observable。 There are additional changes required as this is asynchronous

const  of , throwError, operators: 
    catchError
  
 = rxjs;

const httpClientServiceMock = 
  put: () => of (
    value: 'test'
  )
;

const httpService = 
  requestCall(url, data, requestConfig) 

    return httpClientServiceMock
      .put(url, data, requestConfig)
      .pipe(catchError((error) => this.handleError(error)));
  ,
  handleError(error) 
    return throwError();
  
;
const ApiMethod = 
  PUT: ''




const 
  expect,
  test,
  run,
  it,
  describe,
  jest
 = jestLite.core;

describe('httpService', () => 

  it(`should call the 'handleError' method when a request to store data was not successful`, done => 
    const error = 
      status: 401,
      message: 'You are not logged in',
    

    jest.spyOn(httpClientServiceMock, 'put').mockReturnValue(throwError(error));
    const spy = jest.spyOn(httpService, 'handleError');

    httpService
      .requestCall('some-url', ApiMethod.PUT, )
      .subscribe(pr => 
        done.fail(new Error(`It shouldn't go this path!`))
      , error => 
        expect(spy).toBeCalled();
        done();
      );

  );

);

run().then(result => 
  console.log(result[0]);
)
<script src="https://cdn.jsdelivr.net/npm/jest-lite@1.0.0-alpha.4/dist/core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>

【讨论】:

这就是为什么应该尽可能避免done 的原因,因为它会产生许多故障点。 expect(spy).toBeCalled(); done(); - 断言失败,从不调用 done 并导致超时,开发人员感到困惑。更好地使用 asynctoPromise 进行 RxJS 测试,至少对于已完成的 observables。【参考方案2】:

正如在另一个答案中已经指出的那样,您必须订阅返回的 observable。

我只是想添加另一种使用 ma​​rble-testing 的方法,因此您不必手动订阅该 observable:

let testScheduler;

beforeEach(() => testScheduler = new TestScheduler(assertionFn))

it(`should call the 'handleError' method when a request to store data was not successful`, () => 
  const error = 
    status: 401,
    message: 'You are not logged in',
   as HttpErrorResponse;

  jest.spyOn(httpClientServiceMock, 'put').mockReturnValue(throwError(error));
  const spy = jest.spyOn(httpService, 'handleError');

  testScheduler.run(( cold, expectObservable ) => 
    const src$ = httpService.requestCall('some-url', ApiMethod.PUT, );
    
    expectObservable(src$).toBe('#', null,  error: error.message, status: error.status );
    expect(spy).toBeCalled();
  );
);

TestSchedulerrxjs/testing 中可用,run 的回调提供了几个帮助器,例如:coldhotflushexpectObservableexpectSubscriptionstime .

我个人喜欢这一切的是一切都是同步的,所以在采用这种方法时,您可能不必调用done()

【讨论】:

以上是关于模拟 Observable 以在 Jest 中抛出错误的主要内容,如果未能解决你的问题,请参考以下文章

通过 jest mock 测试 catch 块

使用 Mockito 从模拟中抛出已检查的异常

在模拟器中正常,但在 ios 设备中抛出异常

如何在可观察的地图中抛出Error(rxjs6,ng6)

笑话:模拟 RxJs 管道

Jest - 在另一个模块函数中模拟函数