如何在redux中单元测试/存根异步调用

时间:2017-02-10 15:11:42

标签: javascript unit-testing asynchronous redux contentful

如何正确测试以下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)
      })
  })
})

2 个答案:

答案 0 :(得分:0)

IMO嘲弄getEntries(可能createClient)似乎是正确的做法。 :) 这取决于您加载contentful sdk的方式。我看到你正在使用ES Modules和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作为所有动作的依赖关系传递给我?这样,模拟会变得更容易,当你还有几个动作模块时,很可能没有必要多次初始化客户端?

答案 1 :(得分:0)

我以这种方式解决了。 首先,我使用函数initClient()和getClient()将客户端的创建移动到自己的文件中。该模块称为contentfulClient。

然后,我发现可以在sinon中模拟实例化对象的功能:

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)
      })