如何在 redux 中单元 test/stub 异步调用
How to unit test/stub async call in redux
对以下 redux 异步操作进行单元测试的正确方法是什么?
const client = contentful.createClient(clientConfig);
export const fetchNavigation = () => {
return dispatch => {
return client.getEntries({content_type: 'navigation'})
.then((entries) => {
console.log('All entries for content_type = navigation')
dispatch(receiveNavigation(entries))
})
.catch(error => {
console.log('Something went wrong');
dispatch(fetchNavigationFailure(error));
});
}
}
我不知道如何自定义 client.getEntries 执行的 Web 请求响应正文。我认为用我自己的函数替换 getEntries 函数就可以了。但是,我不知道从哪里开始做。
这是我写的单元测试:
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('fetchNavigation', () => {
it('creates RECEIVE_NAVIGATION when fetching navigation is done', () => {
// Here I should prepare the client.getEntries() returned promise
const expectedBodyResponse = { includes: ['do something', 'yay!'] }
const expectedActions = [
{ type: actions.RECEIVE_NAVIGATION, navigation: expectedBodyResponse }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchNavigation())
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
IMO 嘲笑 getEntries
(可能还有 createClient
)似乎是正确的做法。 :)
这取决于您如何加载 contentful
sdk。正如我所见,您正在使用 ES 模块和 Jasmine,对吗?
要模拟 getEntries
函数,您必须模拟 createClient
,因为无法从您的测试中访问客户端。
我想这个 this answer 可能就是您要找的。
我刚刚写了一个例子。
import contentful from 'contentful';
export const fetchNavigation = () => {
return (dispatch) => {
return contentful.createClient({ accessToken: 'fooo', space: 'bar' })
.getEntries({ content_type: 'navigation' })
.then(() => {
dispatch('yeah');
})
.catch(error => console.error('Something went wrong', error));
};
};
import { fetchNavigation } from '../Action';
import * as contentful from 'contentful';
describe('Contentful mocking', () => {
it('should be possible to mock Contentful', (done) => {
const client = { getEntries: () => { return Promise.resolve(); } };
const spy = {
fn: (value) => {
expect(value).toBe('yeah');
done();
},
};
spyOn(contentful.default, 'createClient').and.returnValue(client);
fetchNavigation()(spy.fn);
});
});
我不得不将 createClient
调用移动到操作本身中,否则我认为当它隐藏在模块范围内时无法访问和模拟它。然后我使用 import * as contentful from 'contentful'
来模拟和覆盖所需的功能,并可以灵活地根据我的需要调整一切。
createClient
的用法对我来说有点不幸。我可能会稍微重组所有内容并将 client
作为所有操作的依赖项传递?这样模拟会变得更容易,当你也有几个动作模块时,很可能不需要多次初始化客户端?
我是这样解决的。
首先,我使用函数 initClient() 和 getClient() 将客户端的创建移动到它自己的文件中。该模块称为 contentfulClient。
然后,我发现可以在sinon中mock实例化对象的功能:
import * as contentful from './services/contentfulClient';
const client = contentful.initClient(clientConfig);
const navigation = {
items: ['page1', 'page2']
};
// Returns a promise with navigation as content
sinon.stub(client, 'getEntries').resolves(navigation);
// Assert
return store.dispatch(actions.fetchNavigation())
.then( () => { expect(store.getActions()).toEqual(expectedActions)
})
对以下 redux 异步操作进行单元测试的正确方法是什么?
const client = contentful.createClient(clientConfig);
export const fetchNavigation = () => {
return dispatch => {
return client.getEntries({content_type: 'navigation'})
.then((entries) => {
console.log('All entries for content_type = navigation')
dispatch(receiveNavigation(entries))
})
.catch(error => {
console.log('Something went wrong');
dispatch(fetchNavigationFailure(error));
});
}
}
我不知道如何自定义 client.getEntries 执行的 Web 请求响应正文。我认为用我自己的函数替换 getEntries 函数就可以了。但是,我不知道从哪里开始做。
这是我写的单元测试:
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('fetchNavigation', () => {
it('creates RECEIVE_NAVIGATION when fetching navigation is done', () => {
// Here I should prepare the client.getEntries() returned promise
const expectedBodyResponse = { includes: ['do something', 'yay!'] }
const expectedActions = [
{ type: actions.RECEIVE_NAVIGATION, navigation: expectedBodyResponse }
]
const store = mockStore({ todos: [] })
return store.dispatch(actions.fetchNavigation())
.then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
IMO 嘲笑 getEntries
(可能还有 createClient
)似乎是正确的做法。 :)
这取决于您如何加载 contentful
sdk。正如我所见,您正在使用 ES 模块和 Jasmine,对吗?
要模拟 getEntries
函数,您必须模拟 createClient
,因为无法从您的测试中访问客户端。
我想这个 this answer 可能就是您要找的。
我刚刚写了一个例子。
import contentful from 'contentful';
export const fetchNavigation = () => {
return (dispatch) => {
return contentful.createClient({ accessToken: 'fooo', space: 'bar' })
.getEntries({ content_type: 'navigation' })
.then(() => {
dispatch('yeah');
})
.catch(error => console.error('Something went wrong', error));
};
};
import { fetchNavigation } from '../Action';
import * as contentful from 'contentful';
describe('Contentful mocking', () => {
it('should be possible to mock Contentful', (done) => {
const client = { getEntries: () => { return Promise.resolve(); } };
const spy = {
fn: (value) => {
expect(value).toBe('yeah');
done();
},
};
spyOn(contentful.default, 'createClient').and.returnValue(client);
fetchNavigation()(spy.fn);
});
});
我不得不将 createClient
调用移动到操作本身中,否则我认为当它隐藏在模块范围内时无法访问和模拟它。然后我使用 import * as contentful from 'contentful'
来模拟和覆盖所需的功能,并可以灵活地根据我的需要调整一切。
createClient
的用法对我来说有点不幸。我可能会稍微重组所有内容并将 client
作为所有操作的依赖项传递?这样模拟会变得更容易,当你也有几个动作模块时,很可能不需要多次初始化客户端?
我是这样解决的。 首先,我使用函数 initClient() 和 getClient() 将客户端的创建移动到它自己的文件中。该模块称为 contentfulClient。
然后,我发现可以在sinon中mock实例化对象的功能:
import * as contentful from './services/contentfulClient';
const client = contentful.initClient(clientConfig);
const navigation = {
items: ['page1', 'page2']
};
// Returns a promise with navigation as content
sinon.stub(client, 'getEntries').resolves(navigation);
// Assert
return store.dispatch(actions.fetchNavigation())
.then( () => { expect(store.getActions()).toEqual(expectedActions)
})