Jest单元测试中Mock Imgur API,如何为上传删除方法编写返回模拟值的测试
问题原因
你当前测试覆盖率低的核心原因有两个:
- 你直接用
jest.spyOn(service, 'upload').mockReturnValue(...)Mock了整个upload方法,导致方法内部的Header构造、FormData拼装、HttpClient调用逻辑完全没有执行,所以对应行没有被覆盖率统计到 - 完全没有编写delete方法的测试用例
Angular的Http服务测试不应该直接Mock服务自身的方法,而是借助HttpClientTestingModule提供的HttpTestingController拦截校验Http请求,既可以验证请求参数的正确性,也能完整执行服务方法内的所有逻辑。
调整后的测试代码
import { ImgurService } from './imgur.service'; import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { ImgurResponse } from '../models/imgur'; // 请替换为你项目中UtilService的实际引入路径 import { UtilService } from './util.service'; describe('ImgurService', () => { let service: ImgurService; let httpTestingController: HttpTestingController; // 模拟随机字符串返回值,避免依赖UtilService的具体实现 const mockRandomStr = 'test_random_string_12345678901234567890'; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ImgurService], }); service = TestBed.inject(ImgurService); httpTestingController = TestBed.inject(HttpTestingController); // Mock UtilService的静态随机字符串生成方法 jest.spyOn(UtilService, 'generateRandomString').mockReturnValue(mockRandomStr); }); // 每次测试结束后校验没有多余的未处理请求 afterEach(() => { httpTestingController.verify(); }); describe('upload()', () => { it('should send correct upload request and return response', (done) => { const testUploadContent = 'test_base64_content'; const testType = 'base64'; 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, }; // 调用upload方法触发请求 service.upload(testUploadContent, testType).subscribe(res => { // 校验返回值符合预期 expect(res).toEqual(mockImgurResponse); done(); }); // 拦截匹配的POST请求 const req = httpTestingController.expectOne('https://api.imgur.com/3/image'); // 校验请求方法正确 expect(req.request.method).toBe('POST'); // 校验请求头携带正确的Authorization expect(req.request.headers.get('Authorization')).toBe('Client-ID'); // 校验FormData参数正确 const formData = req.request.body as FormData; expect(formData.get('image')).toBe(testUploadContent); expect(formData.get('name')).toBe(mockRandomStr); expect(formData.get('type')).toBe(testType); // 刷入模拟返回值结束请求 req.flush(mockImgurResponse); }); }); describe('delete()', () => { it('should send correct delete request and return response', (done) => { const testDeleteId = 'test_img_id'; const mockDeleteResponse: ImgurResponse = { data: {}, success: true, status: 200 }; service.delete(testDeleteId).subscribe(res => { expect(res).toEqual(mockDeleteResponse); done(); }); // 拦截匹配的DELETE请求 const req = httpTestingController.expectOne(`https://api.imgur.com/3/image/${testDeleteId}`); expect(req.request.method).toBe('DELETE'); expect(req.request.headers.get('Authorization')).toBe('Client-ID'); req.flush(mockDeleteResponse); }); }); });
覆盖说明
调整后服务内的所有逻辑都会被完整执行:
- upload方法内的Header构造、FormData拼装、http.post调用全链路都会被覆盖
- delete方法的完整逻辑也会被覆盖
- 额外新增了请求参数、请求头的正确性校验,比原有测试更符合单元测试的验证要求
内容的提问来源于stack exchange,提问作者methuselah
相关产品推荐
相关产品推荐

