【问题标题】:How to unit test/stub async call in redux如何在 redux 中对异步调用进行单元测试/存根
【发布时间】:2017-02-10 15:11:42
【问题描述】:

单元测试以下 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)
      })
  })
})

【问题讨论】:

    标签: javascript unit-testing asynchronous redux contentful


    【解决方案1】:

    IMO 嘲笑 getEntries(可能还有 createClient)似乎是正确的做法。 :) 这取决于您如何加载contentful sdk。正如我所见,您正在使用 ES 模块和 Jasmine,对吗?

    要模拟 getEntries 函数,您必须模拟 createClient,因为无法从您的测试中访问客户端。

    我认为this answer 可能就是您要找的。​​p>

    我只是写了一个例子。

    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 作为所有操作的依赖项传递?这样模拟会变得更容易,而且当您还有多个动作模块时,很可能不需要多次初始化客户端?

    【讨论】:

    • 非常感谢 Stefan 的回答。
    【解决方案2】:

    我就是这样解决的。 首先,我使用函数 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)
          })

    【讨论】:

      猜你喜欢
      • 2017-04-14
      • 2016-03-19
      • 2020-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-25
      • 1970-01-01
      • 2013-03-21
      相关资源
      最近更新 更多