Reactjs 使用Jest在Redux thunk中测试已调度的操作
我对开玩笑还不太熟悉,而且我不擅长测试异步代码 我使用了一个简单的Reactjs 使用Jest在Redux thunk中测试已调度的操作,reactjs,redux,fetch,jestjs,redux-thunk,Reactjs,Redux,Fetch,Jestjs,Redux Thunk,我对开玩笑还不太熟悉,而且我不擅长测试异步代码 我使用了一个简单的Fetch助手: export function fetchHelper(url, opts) { return fetch(url, options) .then((response) => { if (response.ok) { return Promise.resolve(response); }
Fetch
助手:
export function fetchHelper(url, opts) {
return fetch(url, options)
.then((response) => {
if (response.ok) {
return Promise.resolve(response);
}
const error = new Error(response.statusText || response.status);
error.response = response;
return Promise.reject(error);
});
}
并这样实施:
export function getSomeData() {
return (dispatch) => {
return fetchHelper('http://datasource.com/').then((res) => {
dispatch(setLoading(true));
return res.json();
}).then((data) => {
dispatch(setData(data));
dispatch(setLoading(false));
}).catch(() => {
dispatch(setFail());
dispatch(setLoading(false));
});
};
}
但是,我想测试在正确的情况下以正确的顺序触发正确的调度
使用sinon.spy()
,这过去非常容易,但我不太明白如何在玩笑中复制它。理想情况下,我希望我的测试看起来像这样:
expect(spy.args[0][0]).toBe({
type: SET_LOADING_STATE,
value: true,
});
expect(spy.args[1][0]).toBe({
type: SET_DATA,
value: {...},
});
提前感谢您的帮助或建议 如果您使用
jest.fn()
模拟分派函数,您可以访问dispatch.mock.calls
以获取对存根的所有调用
const dispatch = jest.fn();
actions.yourAction()(dispatch);
expect(dispatch.mock.calls.length).toBe(1);
expect(dispatch.mock.calls[0]).toBe({
type: SET_DATA,
value: {...},
});
redux文档具有很好的功能:
对于使用或其他中间件的异步操作创建者,最好完全模拟Redux存储进行测试。您可以使用将中间件应用于模拟存储。您还可以使用来模拟HTTP请求 他们的方法不是使用jest(或sinon)进行间谍,而是使用模拟存储并断言已调度的操作。这样做的好处是能够处理thunk发送thunk,这对于间谍来说是非常困难的
这些都是直接从文档中获得的,但是如果您想让我为您的thunk创建一个示例,请告诉我。对于使用Redux thunk或其他中间件的异步操作创建者,最好完全模拟Redux存储进行测试。您可以使用
redux mock store
将中间件应用于模拟存储。为了模拟HTTP请求,可以使用nock
根据,您需要在测试异步操作的请求结束时调用store.getActions()
,您可以像
mockStore(getState?:对象,函数)=>store:Function
返回
已配置的模拟存储的实例。如果你想重新设置你的商店
每次测试后,您都应该调用此函数
store.dispatch(action)=>action
通过
模拟商店。该操作将存储在实例内的数组中
然后被处决
store.getState()=>state:Object
返回模拟的状态
贮藏
store.getActions()=>actions:Array
返回模拟的操作
贮藏
store.clearActions()
清除存储的操作
您可以像这样编写测试操作
import nock from 'nock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
//Configuring a mockStore
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
//Import your actions here
import {setLoading, setData, setFail} from '/path/to/actions';
test('test getSomeData', () => {
const store = mockStore({});
nock('http://datasource.com/', {
reqheaders // you can optionally pass the headers here
}).reply(200, yourMockResponseHere);
const expectedActions = [
setLoading(true),
setData(yourMockResponseHere),
setLoading(false)
];
const dispatchedStore = store.dispatch(
getSomeData()
);
return dispatchedStore.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
p.S.请注意,当模拟操作被触发时,模拟存储不会自动更新,如果您依赖于上一个操作后更新的数据,以便在下一个操作中使用,则需要编写自己的模拟存储实例,如
const getMockStore = (actions) => {
//action returns the sequence of actions fired and
// hence you can return the store values based the action
if(typeof action[0] === 'undefined') {
return {
reducer: {isLoading: true}
}
} else {
// loop over the actions here and implement what you need just like reducer
}
}
然后像这样配置mockStore
const store = mockStore(getMockStore);
希望能有帮助。另外,请查看redux文档中关于测试异步动作创建者的,在我的回答中,我使用的是
axios
,而不是fetch
,因为我在fetch承诺方面没有太多经验,这与您的问题无关。我个人觉得使用axios非常舒服请看下面我提供的代码示例:
// apiCalls.js
const fetchHelper = (url) => {
return axios.get(url);
}
import * as apiCalls from './apiCalls'
describe('getSomeData', () => {
it('should dispatch SET_LOADING_STATE on start of call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_LOADING_STATE,
value: true,
});
});
it('should dispatch SET_DATA action on successful api call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_DATA,
value: { ...},
});
});
it('should dispatch SET_FAIL action on failed api call', async () => {
spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.reject());
const mockDispatch = jest.fn();
await getSomeData()(mockDispatch);
expect(mockDispatch).toHaveBeenCalledWith({
type: SET_FAIL,
});
});
});
在这里,我模拟fetch助手返回解析的promise to test success部分,并拒绝promise to test failed api调用。您也可以将参数传递给它们,以便在响应时进行验证。您可以像这样实现
getSomeData
:const getSomeData = () => {
return (dispatch) => {
dispatch(setLoading(true));
return fetchHelper('http://datasource.com/')
.then(response => {
dispatch(setData(response.data));
dispatch(setLoading(false));
})
.catch(error => {
dispatch(setFail());
dispatch(setLoading(false));
})
}
}
我希望这能解决你的问题。如果您需要任何澄清,请发表评论。另外,您可以通过查看上面的代码来了解为什么我更喜欢axios而不是fetch,从而使您免于许下许多多承诺
关于它的进一步阅读,您可以参考:谢谢@Canastro,但这似乎对我不起作用。当我格式化调度时,就像你有
actions.yourAction()(调度)一样代码>它抛出一个错误。我试着这样做dispatch(actions.urnSearch(id))代码>当我记录dispatch.mock.calls[0]
时,它只会给我[[Function]]
我可以在这方面获得一些帮助吗?如果你从动作创建者那里调度其他动作,这是行不通的。使用redux应该避免在一行中调度多个动作。您有dispatch(setData(data));调度(设置加载(假))代码>将触发2个存储更改和2个渲染。如果您将其合并到单个操作中,并将该操作的加载状态设置为false,那么您的应用程序中只有1个重新渲染。您是否有指向thunks调度thunks的任何文档的链接?我看不出他们是否被派去了,手头没有。我在测试thunks时看到的一个常见问题是,expect调用是在异步调用完成之前进行的(例如,它们不返回或等待承诺),所以请仔细检查这类事情。如果不是这样的话,你最好用更多的细节来回答你自己的问题。我能在这方面得到一些帮助吗?我能在这方面得到一些帮助吗
const getSomeData = () => {
return (dispatch) => {
dispatch(setLoading(true));
return fetchHelper('http://datasource.com/')
.then(response => {
dispatch(setData(response.data));
dispatch(setLoading(false));
})
.catch(error => {
dispatch(setFail());
dispatch(setLoading(false));
})
}
}