在 Jest 单元测试中模拟 imgur API 调用

Posted

技术标签:

【中文标题】在 Jest 单元测试中模拟 imgur API 调用【英文标题】:Mocking imgur API calls in Jest unit test 【发布时间】:2021-11-05 05:57:50 【问题描述】:

如何为以下返回模拟值的上传和删除方法编写测试?

@Injectable()
export class ImgurService 
  private readonly IMGUR_API_URL = 'https://api.imgur.com/3/image';
  private readonly IMGUR_CLIENT_ID = 'Client-ID';
 
  constructor(private http: HttpClient) 
 
  upload(upload: string | File, type = 'base64'): Observable<ImgurResponse> 
    const headers = new HttpHeaders().set('Authorization', `$this.IMGUR_CLIENT_ID`);
    const formData = new FormData();
    formData.append('image', upload);
    formData.append('name', UtilService.generateRandomString(32));
    formData.append('type', type);
    return this.http.post<ImgurResponse>(`$this.IMGUR_API_URL`, formData, 
      headers,
    );
  
 
  delete(id: string): Observable<ImgurResponse> 
    const headers = new HttpHeaders().set('Authorization', `$this.IMGUR_CLIENT_ID`);
    return this.http.delete<ImgurResponse>(`$this.IMGUR_API_URL/$id`,  headers );
  

这是我到目前为止的测试逻辑,到目前为止,它按预期运行:

import  ImgurService  from './imgur.service';
import  TestBed  from '@angular/core/testing';
import  HttpClientTestingModule  from '@angular/common/http/testing';
import  of  from 'rxjs';
import  ImgurResponse  from '../models/imgur';

describe('ImgurService', () => 
  let service: ImgurService;

  beforeEach(() => 
    TestBed.configureTestingModule(
      imports: [HttpClientTestingModule],
      providers: [ImgurService],
    );
    service = TestBed.inject(ImgurService);
  );

  describe('upload()', () => 
    it('should upload file', () => 
      const mockImgurResponse: ImgurResponse = 
        data: 
          id: 'orunSTu',
          title: null,
          description: null,
          datetime: 1587998106,
          type: 'image/png',
          animated: false,
          width: 2100,
          height: 1709,
          size: 138557,
          views: 0,
          bandwidth: 0,
          vote: null,
          favorite: false,
          nsfw: null,
          section: null,
          account_url: null,
          account_id: 0,
          is_ad: false,
          in_most_viral: false,
          tags: [],
          ad_type: 0,
          ad_url: '',
          in_gallery: false,
          deletehash: 'N9YaI4CIkq3rIar',
          name: 'Hero Image',
          link: 'https://i.imgur.com/keznKEA.png',
        ,
        success: true,
        status: 200,
      ;
      jest.spyOn(service, 'upload').mockReturnValue(of(mockImgurResponse));
      expect(service.upload('test')).toBeDefined();
    );
  );
);

但是,我的测试覆盖率非常低:

------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
 imgur.service.ts |   52.94 |        0 |   33.33 |   46.67 | 15-27             
------------------|---------|----------|---------|---------|-------------------

【问题讨论】:

您的测试覆盖率低应该不足为奇;实际的实现不参与测试。不要嘲笑你应该测试的东西。当你犯这个错误时,你可能会发现 TDD 工作流更明显,因为在你实际编写方法之前通过的测试会暴露它一点。 谢谢乔恩,我再看看。 设法弄清楚使用 HTTP 请求模拟。 【参考方案1】:

我通过拦截请求并使用我们的模拟数据覆盖任何调用来解决此问题。在下面的示例中,如果 Imgur 的 API 被命中,我们希望使用我们之前定义的模拟数据来测试我们的服务。

import  ImgurService  from './imgur.service';
import  TestBed  from '@angular/core/testing';
import  HttpClientTestingModule  from '@angular/common/http/testing';
import  Observable, of  from 'rxjs';
import  ImgurResponse  from '../models/imgur';
import  Injectable  from '@angular/core';
import 
  HTTP_INTERCEPTORS,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
  HttpResponse,
 from '@angular/common/http';

@Injectable()
export class ImgurServiceInterceptorMock implements HttpInterceptor 
  mockImgurResponse: ImgurResponse = 
    data: 
      id: 'orunSTu',
      title: null,
      description: null,
      datetime: 1587998106,
      type: 'image/png',
      animated: false,
      width: 2100,
      height: 1709,
      size: 138557,
      views: 0,
      bandwidth: 0,
      vote: null,
      favorite: false,
      nsfw: null,
      section: null,
      account_url: null,
      account_id: 0,
      is_ad: false,
      in_most_viral: false,
      tags: [],
      ad_type: 0,
      ad_url: '',
      in_gallery: false,
      deletehash: 'N9YaI4CIkq3rIar',
      name: 'Hero Image',
      link: 'https://i.imgur.com/keznKEA.png',
    ,
    success: true,
    status: 200,
  ;

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> 
    if (request.method === 'POST') 
      return of(new HttpResponse( status: 200, body: this.mockImgurResponse ));
    
    if (request.method === 'DELETE') 
      return of(new HttpResponse( status: 200, body: this.mockImgurResponse ));
    
    next.handle(request);
  


describe('ImgurService', () => 
  let service: ImgurService;
  const mockImgurResponse: ImgurResponse = 
    data: 
      id: 'orunSTu',
      title: null,
      description: null,
      datetime: 1587998106,
      type: 'image/png',
      animated: false,
      width: 2100,
      height: 1709,
      size: 138557,
      views: 0,
      bandwidth: 0,
      vote: null,
      favorite: false,
      nsfw: null,
      section: null,
      account_url: null,
      account_id: 0,
      is_ad: false,
      in_most_viral: false,
      tags: [],
      ad_type: 0,
      ad_url: '',
      in_gallery: false,
      deletehash: 'N9YaI4CIkq3rIar',
      name: 'Hero Image',
      link: 'https://i.imgur.com/keznKEA.png',
    ,
    success: true,
    status: 200,
  ;

  beforeEach(() => 
    TestBed.configureTestingModule(
      imports: [HttpClientTestingModule],
      providers: [
        ImgurService,
        
          provide: HTTP_INTERCEPTORS,
          useClass: ImgurServiceInterceptorMock,
          multi: true,
        ,
      ],
    );
    service = TestBed.inject(ImgurService);
  );

  describe('upload()', () => 
    it('should upload file', () => 
      expect(
        service.upload('test').subscribe((result) => 
          expect(result).toEqual(mockImgurResponse);
        )
      );
    );
  );

  describe('delete()', () => 
    it('should delete file', () => 
      expect(
        service.delete('test').subscribe((result) => 
          expect(result).toEqual(mockImgurResponse);
        )
      );
    );
  );
);

【讨论】:

以上是关于在 Jest 单元测试中模拟 imgur API 调用的主要内容,如果未能解决你的问题,请参考以下文章

模拟安装挂钩 Jest 测试单元

用 Jest 模拟 Firebase 功能(单元测试)

如何模拟在使用 Jest 测试的 React 组件中进行的 API 调用

如何在单元测试期间使用 vue-test-utils 和 jest 模拟 mixin?

用 Jest 反应单元测试 - 模拟 localStorage 的问题

如何使用 NextJS API 在 Jest 单元测试中考虑 Google reCaptcha