8.3.2 异步action构造函数测试
您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!
8.3.2 异步action构造函数测试
异步action构造函数因为存在副作用,所以单元测试会比普通action构造函数复杂。
一个异步action对象就是一个函数,被派发到redux-thunk中间件时会被执行,产生副作用,以第7章中的weather_redux/src/weather/actions.js异步action对象构造器fetch-Weather为例,产生的异步动作被派发之后,会连续派发另外两个action对象代表fetch开始和fetch结束,单元测试要做的就是验证这样的行为。
被测试对象fetchWeather发挥作用需要使用redux-thunk中间件,所以需要一个Redux Store,在fetchWeather函数中还需要调用dispatch函数,而dispatch函数也是来自于一个Redux Store。但是我们并没有必要创建一个完整功能的Redux Store,使用redux-mock-store更加合适,因为在单元测试环境下,dispatch函数最好不要做实际的派发动作,只要能够把被派发的对象记录下来,留在验证阶段读取就可以了。
使用redux-mock-store的代码如下:
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
const middlewares = [thunk];
const createMockStore = configureStore(middlewares);
最后得到的是一个createMockStore函数,在测试用例中我们会用这个函数而不是redux提供的createStore函数,注意createMockStore可以使用Redux中间件,添加了redux-thunk之后可以处理异步action对象。
fetchWeather函数中会调用fetch函数,这个函数的行为是去访问指定的URL来获取资源。单元测试应该独立而且稳定,当然不应该在单元测试中访问网络资源,所以需要“篡改”fetch函数的行为,感谢sinon,这样的篡改工作非常简单,代码如下:
import {stub} from 'sinon';
describe('fetchWeather', () => {
let stubbedFetch;
beforeEach(() => {
stubbedFetch = stub(global, 'fetch');
});
afterEach(() => {
stubbedFetch.restore();
});
});
通过sinon提供的stub函数来“篡改”函数行为,stub第一个参数是一个对象,第二个参数是这个函数的字符串名,返回一个stub对象,通过这个stub上的对象可以指定被“篡改”函数的行为。通过stub函数实际上可以“篡改”任何一个函数的行为,对fetch这样的全局函数也不例外,因为全局函数相当于在global对象上的一个函数。
需要注意的是,每一个单元测试都应该把环境清理干净。所以对一个测试套件惯常的做法是在beforeEach中创造stub对象,在afterEach函数中用stub对象的restore方法恢复被“篡改”函数原本的行为。
fetchWeather函数的测试用例代码如下:
it('should dispatch fetchWeatherSuccess action type on fetch success', () => {
const mockResponse= Promise.resolve({
status: 200,
json: () => Promise.resolve({
weatherinfo: {}
})
});
stubbedFetch.returns(mockResponse);
return store.dispatch(actions.fetchWeather(1)).then(() => {
const dispatchedActions = store.getActions();
expect(dispatchedActions.length).toBe(2);
expect(dispatchedActions[0].type).toBe(actionTypes.FETCH_STARTED);
expect(dispatchedActions[1].type).toBe(actionTypes.FETCH_SUCCESS);
});
});
在上面的测试用例中,利用beforeEach中创造的stub对象stubbedFetch规定fetch函数被调用时返回一个指定的mockReposonse。这样,fetchWeather函数中的fetch函数行为就完全被操纵,毕竟我们并不需要测试fetch函数的行为,所以只需要让fetch函数返回我们想要的结果就行。
fetchWeather是一个异步action构造函数,测试一个涉及异步的被测函数时,就不能像测试普通函数一样预期被测函数执行结束就可以验证结果了。
在上面的例子中,虽然mockResponse是通过Promise.resolve函数产生的“创造即已经完结的”Promise对象,但是其then指定的函数依然要等到Node.js的下一个时钟周期才执行,所以也不能在fetchWeather函数执行完之后就认为异步操作就已经完结。
在Jest中测试异步函数有两种方法,一种是代表测试用例的函数增加一个参数,习惯上这个参数叫做done,Jest发现这个参数存在就会认为这个参数是一个回调函数,只有这个回调函数被执行才算是测试用例结束。
测试用例使用done参数的例子如下:
it('should timeout', (done) => {
});
在上面的例子中,这个it测试用例最终会因为超时而失败,因为没有任何代码去调用done函数。
除了使用done参数,还有另外一个方法,就是让测试用例函数返回一个Promise对象,这样也等于告诉Jest这个测试用例是一个异步过程,只有当返回的Promise对象完结的时候,这个测试用例才算结束。
在fetchWeather的测试用例中,就使用了返回Promise对象的方法。
注意,fetchWeather的测试用例返回的并不是store.dispatch函数返回的那个Promise对象,而是经过then函数产生的一个新的Promise对象,所以当Jest获取的Promise对象被认为是完结时,在then函数中的所有断言语句绝对已经执行完毕了。
断言部分我们使用了redux-mock-store所创造Store的getActions函数,注意这个函数并不是Redux的功能,但能够帮助我们读取到所有派发到Store上的actions,在单元测试中非常适用。 深入浅出React和Redux