这是我第一次进行测试,并且获得了测试UI组件的技巧。现在,我正在尝试测试其中包含一些静态方法的类。它也包含参数。
见上课:
import UserInfoModel from '../models/UserInfo.model';
import ApiClient from './apiClient';
import ApiNormalizer from './apiNormalizer';
import Article from '../models/Article.model';
import Notification from '../models/Notification.model';
import Content from '../models/Link.model';
export interface ResponseData {
[key: string]: any;
}
export default class ApiService {
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then(res => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
static getProfile(callback: (a: UserInfoModel) => void) {
return ApiService.makeApiCall(`profile`, ApiNormalizer.normalizeProfile, callback);
}
}
我已经创建了一个通过的小测试,但是我不确定自己在做什么。
// @ts-ignore
import moxios from 'moxios';
import axios from 'axios';
import { baseURL } from './apiClient';
import { dummyUserInfo } from './../models/UserInfo.model';
describe('apiService', () => {
let axiosInstance: any;
beforeEach(() => {
axiosInstance = axios.create();
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
it('should perform get profile call', done => {
moxios.stubRequest(`${baseURL.DEV}profile`, {
status: 200,
response: {
_user: dummyUserInfo
}
});
axiosInstance
.get(`${baseURL.DEV}profile`)
.then((res: any) => {
expect(res.status).toEqual(200);
expect(res.data._user).toEqual(dummyUserInfo);
})
.finally(done);
});
});
我正在使用moxios测试axios的内容-> https://github.com/axios/moxios
那么哪种方法可以测试此类的正确方法呢?
最佳答案
介绍
Unit tests是由软件开发人员编写和运行的自动化测试,以确保应用程序的某个部分符合其设计并按预期运行。就像我们在谈论面向对象的编程一样,一个单元通常是一个完整的接口(interface),例如一个类,但是可以是一个单独的方法。
单元测试的目的是隔离程序的每个部分,并表明各个部分是正确的。因此,如果我们考虑您的ApiService.makeApiCall
函数:
static makeApiCall(
url: string,
normalizeCallback: (d: ResponseData) => ResponseData | null,
callback: (d: any) => any
) {
return ApiClient.get(url)
.then((res: any) => {
callback(normalizeCallback(res.data));
})
.catch(error => {
console.error(error);
});
}
我们可以看到它有一个外部资源调用
ApiClient.get
,应该是mocked。在这种情况下,模拟HTTP请求并不是完全正确的,因为ApiService
不会直接利用它们,在这种情况下,您的单元变得比预期的要宽一些。模拟
Jest框架提供了mocking的强大机制,Omair Nabiel的示例是正确的。但是,我不仅希望对具有预定义数据的函数进行 stub ,而且还要检查被 stub 的函数是否被调用了预期的次数(因此请使用模拟的真实本质)。因此,完整的模拟示例如下所示:
/**
* Importing `ApiClient` directly in order to reference it later
*/
import ApiClient from './apiClient';
/**
* Mocking `ApiClient` with some fake data provider
*/
const mockData = {};
jest.mock('./apiClient', function () {
return {
get: jest.fn((url: string) => {
return Promise.resolve({data: mockData});
})
}
});
这允许向您的测试示例添加其他断言:
it('should call api client method', () => {
ApiService.makeApiCall('test url', (data) => data, (res) => res);
/**
* Checking `ApiClient.get` to be called desired number of times
* with correct arguments
*/
expect(ApiClient.get).toBeCalledTimes(1);
expect(ApiClient.get).toBeCalledWith('test url');
});
正面测试
因此,只要我们弄清楚了什么以及如何模拟数据,就让我们找出应该测试的内容。好的测试应涵盖two situations:积极测试-通过提供有效数据来测试系统,负面测试-通过提供无效数据来测试系统。以我的拙见,应该添加第三个分支-边界测试-测试,重点放在要测试的软件的边界或限制条件上。如果您对其他类型的测试感兴趣,请引用此Glossary。
因此,
makeApiCall
方法的正向测试流程应调用normalizeCallback
和callback
方法,我们可以按以下方式编写此测试(但是,有多种方法可以给猫换皮): it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
expect(secondCallback).toBeCalledWith(firstCallback(mockData));
done();
});
});
请注意此测试中的几件事:
-由于此测试的异步性质,我正在使用
done
回调来让测试知道该测试已完成-我使用的
mockData
变量模拟了ApiClient.get
的数据,因此我检查了回调是否获得了正确的值-
mockData
和类似变量应从mock
开始。否则,Jest将不允许将其从模拟scope中删除阴性测试
测试的负面方式看起来很相似。
ApiClient.get
方法应该抛出错误,而ApiService
应该对其进行处理并放入console
中。另外,我正在检查没有回调被调用。import ApiService from './api.service';
const mockError = {message: 'Smth Bad Happened'};
jest.mock('./apiClient', function () {
return {
get: jest.fn().mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
}
});
describe( 't1', () => {
it('should handle error', (done) => {
console.error = jest.fn();
const firstCallback = jest.fn((data: any) => {
return data;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(0);
expect(secondCallback).toBeCalledTimes(0);
expect(console.error).toBeCalledTimes(1);
expect(console.error).toBeCalledWith(mockError);
done();
});
});
});
边界测试
边界测试可能在您的情况下争论不休,但是只要(根据您的类型定义
normalizeCallback: (d: ResponseData) => ResponseData | null
)第一个回调可以返回null
,这是一种很好的做法,检查是否已成功转移到第二个回调而没有任何错误或异常。我们可以稍微重写一下第二个测试:it('should call callbacks consequently', (done) => {
const firstCallback = jest.fn((data: any) => {
return null;
});
const secondCallback = jest.fn((data: any) => {
return data;
});
ApiService.makeApiCall('test url', firstCallback, secondCallback)
.then(() => {
expect(firstCallback).toBeCalledTimes(1);
expect(firstCallback).toBeCalledWith(mockData);
expect(secondCallback).toBeCalledTimes(1);
done();
});
});
测试异步代码
关于测试异步代码,您可以阅读全面的文档here。主要思想是,当您拥有异步运行的代码时,Jest需要知道它所测试的代码何时完成,然后才能继续进行另一项测试。 Jest提供了三种方法来实现此目的:
it('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
Jest将等待直到完成的回调被调用,然后再完成测试。如果从不调用
done()
,则测试将失败,这就是您想要发生的情况。 如果您的代码使用Promise,则有一种更简单的方式来处理异步测试。只需从测试中返回一个 promise ,Jest将等待该 promise 解决。如果 promise 被拒绝,则测试将自动失败。
async/await
语法您可以在测试中使用
async
和await
。要编写异步测试,只需在传递给测试的函数前面使用async
关键字即可。it('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
例子
在这里,您可以找到可立即使用的代码示例
https://github.com/SergeyMell/jest-experiments
请让我知道是否有什么不清楚的地方。
更新(29.08.2019)
关于你的问题
根据documentation的说法,Jest会自动将
jest.mock
调用提升到模块的顶部(在导入之前)。看来您可以改用setMock
或doMock
,但是,有issues带有这种 mock 的方式,开发人员有时会遇到这种情况。可以使用require
而不是import
和其他技巧(请参阅this article)来覆盖它们,但是我不喜欢这种方式。在这种情况下,对我而言正确的方法是拆分模拟的定义和实现,因此您声明该模块将像这样被模拟
jest.mock('./apiClient', function () {
return {
get: jest.fn()
}
});
但是,根据测试范围,模拟功能的实现有所不同:
describe('api service success flow', () => {
beforeAll(() => {
//@ts-ignore
ApiClient.get.mockImplementation((url: string) => {
return Promise.resolve({data: mockData});
})
});
...
});
describe('api service error flow', () => {
beforeAll(() => {
//@ts-ignore
ApiClient.get.mockImplementation((url: string) => {
console.log('error result');
return Promise.reject(mockError);
})
});
...
});
据我所知,这将允许您将所有与
api service
相关的流存储在单个文件中。我已经用
api.spec.ts
更新了github example,它实现了上述所有功能。请看一下。关于javascript - 如何测试包含导入的异步方法的类?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57471357/