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