首页 > 解决方案 > 在 Jest 单元测试中模拟 imgur API 调用

问题描述

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

@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             
------------------|---------|----------|---------|---------|-------------------

标签: javascriptangulartypescriptjestjs

解决方案


我通过拦截请求并用我们的模拟数据覆盖任何调用来解决这个问题。在下面的示例中,如果 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);
        })
      );
    });
  });
});

推荐阅读