Sinon spy with isomorphic-fetch 功能

Sinon spy with isomorphic-fetch

我创建了一个简单的 thunk 操作来从 API 获取数据。它看起来像这样:

import fetch from 'isomorphic-fetch';

function json(response) {
  return response.json();
}

/**
 * Fetches booksfrom the server
 */
export function getBooks() {
  return function(dispatch) {
    return fetch("http://localhost:1357/book", {mode: "cors"})
    .then(json)
    .then(function(data) {
      dispatch({
        type: "GET_Books",
        books: data
      });
      // This lets us use promises if we want
      return(data);
    });
  }
};

然后,我写了一个这样的测试:

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {getBooks} from '../../actions/getBooks';
import nock from 'nock';
import fetch from 'isomorphic-fetch';
import sinon from 'sinon';

it('returns the found devices', () => {
  var devices = nock("http://localhost:1357")
                .get("/book")
                .reply(200,
                      {});
  const store = mockStore({devices: []});
  var spy = sinon.spy(fetch);
  return store.dispatch(getBooks()).then(() => {
  }).catch((err) => {
  }).then(() => {
    // https://gist.github.com/jish/e9bcd75e391a2b21206b
    expect(spy.callCount).toEqual(1);
    spy.retore();
  });
});

此测试失败 - 调用计数是 0,而不是 1。为什么 sinon 不模拟函数,我需要做什么才能让它模拟函数?

您正在您的测试文件中导入 fetch 而不是在任何地方调用它。这就是调用计数为零的原因。

这引出了一个问题,即当测试描述为 "returns the found devices" 时,为什么要测试首先调用动作创建者。

thunk 动作创建者的主要目的是成为一个动作创建者,returns 一个可以在以后调用的函数。稍后调用的这个函数可以接收商店分派和状态作为其参数。这允许返回的函数异步分派其他操作。

当您测试 thunk 动作创建器时,您应该关注在以下情况下是否调度了正确的动作。

  1. 请求已发出

  2. 收到响应,获取成功

  3. 发生错误,获取失败

尝试如下操作:

export function fetchBooksRequest () {
  return {
    type: 'FETCH_BOOKS_REQUEST'
  }
}

export function fetchBooksSuccess (books) {
  return {
    type: 'FETCH_BOOKS_SUCCESS',
    books: books
  }
}

export function fetchBooksFailure (err) {
  return {
    type: 'FETCH_BOOKS_FAILURE',
    err
  }
}


/**
 * Fetches books from the server
 */
export function getBooks() {
  return function(dispatch) {
    dispatch(fetchBooksRequest(data));

    return fetch("http://localhost:1357/book", {mode: "cors"})
    .then(json)
    .then(function(data) {
      dispatch(fetchBooksSuccess(data));
      // This lets us use promises if we want
      return(data);
    }).catch(function(err) {
      dispatch(fetchBooksFailure(err));
    })
  }
};

Tests.js

import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import fetchMock from 'fetch-mock'  // You can use any http mocking library

import {getBooks} from '../../actions/getBooks';

const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)

describe('Test thunk action creator', () => {
  it('expected actions should be dispatched on successful request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'FETCH_BOOKS_REQUEST', 
        'FETCH_BOOKS_SUCCESS'
    ]

 // Mock the fetch() global to always return the same value for GET
 // requests to all URLs.
 fetchMock.get('*', { response: 200 })

    return store.dispatch(fetchBooks())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).toEqual(expectedActions)
     })

    fetchMock.restore()
  })

  it('expected actions should be dispatched on failed request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'FETCH_BOOKS_REQUEST', 
        'FETCH_BOOKS_FAILURE'
    ]
 // Mock the fetch() global to always return the same value for GET
 // requests to all URLs.
 fetchMock.get('*', { response: 404 })

    return store.dispatch(fetchBooks())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).toEqual(expectedActions)
     })

    fetchMock.restore()
  })
})