首页 文章

使用redux thunk测试异步操作

提问于
浏览
4

我正在尝试测试具有异步调用的操作 . 我使用Thunk作为我的中间件 . 在下面的操作中,如果服务器返回 OK 响应,我只会调度和更新存储 .

export const SET_SUBSCRIBED = 'SET_SUBSCRIBED'

export const setSubscribed = (subscribed) => {
  return function(dispatch) {
    var url = 'https://api.github.com/users/1/repos';

    return fetch(url, {method: 'GET'})
      .then(function(result) {
        if (result.status === 200) {
          dispatch({
            type: SET_SUBSCRIBED,
            subscribed: subscribed
          })
          return 'result'
        }
        return 'failed' //todo
      }, function(error) {
        return 'error'
      })
  }
}

我无法将测试写入调度,调用或者调用或不调用(取决于服务器响应),或者我可以让调用操作并检查存储中的值是否正确更新 .

我使用fetch-mock来模拟web的fetch()实现 . 但是,看起来 then 中的代码块不会执行 . 我也尝试过使用这个例子没有运气 - http://redux.js.org/docs/recipes/WritingTests.html

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

//passing test
it('returns SET_SUBSCRIBED type and subscribed true', () => {
  fetchMock.get('https://api.github.com/users/1/repos', { status: 200 })

  const subscribed = { type: 'SET_SUBSCRIBED', subscribed: true }
  const store = mockStore({})

  store.dispatch(subscribed)

  const actions = store.getActions()

  expect(actions).toEqual([subscribed])
  fetchMock.restore()
})

//failing test
it('does nothing', () => {
  fetchMock.get('https://api.github.com/users/1/repos', { status: 400 })

  const subscribed = { type: 'SET_SUBSCRIBED', subscribed: true }
  const store = mockStore({})

  store.dispatch(subscribed)

  const actions = store.getActions()

  expect(actions).toEqual([])
  fetchMock.restore()
})

在进一步研究之后,我认为fetch-mock有问题,要么不解析promise,要么执行then语句,要么完全删除fetch . 当我向两个语句添加一个console.log时,没有任何执行 .

What am I doing incorrectly in my tests?

2 回答

  • 14

    您可能希望考虑使用DevTools - 这将使您能够清楚地看到正在调度的操作以及调用之前/之后的状态 . 如果调度从未发生,则可能是获取不返回200类型错误 .

    承诺应该是:

    return fetch(url, {
        method: 'GET'
      })
      .then(function(result) {
        if (result.status === 200) {
          dispatch({
            type: SET_SUBSCRIBED,
            subscribed: subscribed
          })
          return 'result'
        }
        return 'failed' //todo
      }, function(error) {
        return 'error'
      })
    

    等等 - 你会看到.then实际上需要两个独立的功能,一个用于成功,一个用于错误 .

  • 1

    Testing Async Thunk Actions in Redux

    您没有在任何测试中调用setSubscribed redux-thunk动作创建器 . 相反,您正在定义相同类型的新操作,并尝试在测试中分发该操作 .

    在两个测试中,将同步调度以下操作 .

    const subscribed = { type: 'SET_SUBSCRIBED', subscribed: true }
    

    在此操作中,不会向任何API发出请求 .

    我们希望能够从外部API获取,然后在成功或失败时调度操作 .

    由于我们将来在某个时间段调度动作,我们需要使用你的setSubscribed thunk动作创建器 .

    在简要解释了redux-thunk如何工作后,我将解释如何测试这个thunk动作创建者 .

    Actions vs Action Creators

    也许值得解释一下,动作创建者是一个在被调用时返回一个动作对象的函数 .

    术语动作指的是对象本身 . 对于此操作对象,唯一的必需属性是type应该是字符串 .

    例如,这是一个 action creator .

    function addTodo(text) {
      return {
        type: ADD_TODO,
        text
      }
    }
    

    它只是一个返回对象的函数 . We know that this object is a redux action because one of its properties is called type.

    它创建toDos以按需添加 . 让我们做一个新的待办事项来提醒我们狗走路 .

    const walkDogAction = addTodo('walk the dog')
    
    console.log(walkDogAction)
    * 
    * { type: 'ADD_TO_DO, text: 'walk the dog' }
    *
    

    此时我们的动作创建者有一个动作对象 generated .

    现在,如果我们想要将此操作发送到我们的reducer以更新我们的存储,那么我们使用action对象作为参数调用dispatch .

    store.dispatch(walkDogAction)
    

    大 .

    我们已经派出了这个物品,它将直接进入减速器并用新的todo更新我们的商店,提醒我们遛狗 .

    我们如何制定更复杂的行动?如果我希望我的动作创建者执行依赖于异步操作的操作,该怎么办?

    Synchronous vs Asynchronous Redux Actions

    异步(异步)和同步(同步)是什么意思?

    当您同步执行某些操作时,等待它完成后再继续执行其他任务 . 当您异步执行某些操作时,可以在完成之前继续执行另一个任务 .

    好吧,如果我想让我的狗去取东西呢?在这种情况下,我关心三件事

    • 当我让他去取物品的时候

    • 他是否成功取得了什么?

    • 他没有取得对象吗? (即没有棍子回到我身边,在给定的时间后根本没有回到我身边)

    可能很难想象这可以用一个单独的对象来表示,就像我们的addtodo动作一样,它只是由一种类型和一段文本组成 .

    而不是作为对象的动作,它需要是一个功能 . 为什么一个功能?函数可用于分派进一步的操作 .

    我们将fetch的重要总体行动分为三个较小的同步动作 . 我们的主要获取操作创建者是异步的 . 请记住,这个主要动作创建者本身不是一个动作,它仅用于发送进一步的动作 .

    How does a Thunk Action creator work?

    本质上,thunk动作创建者是返回函数而不是对象的动作创建者 . 通过将redux-thunk添加到我们的中间件存储中,这些特殊操作将可以访问商店的dispatch和getState方法 .

    Here is the code inside Redux thunk that does this:
    
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
    

    setSubscribed函数是一个thunk动作创建器,因为它遵循返回一个以dispatch作为参数的函数的签名 .

    好的,这就是为什么我们的thunk动作创建者返回一个函数 . 因为这个函数将被中间件调用并让我们访问调度和获取状态,这意味着我们可以在以后调度进一步的操作 .

    Modelling Asynchronous Operations with Actions

    让我们写下我们的行动 . 我们的redux thunk动作创建者负责异步调度代表你的异步动作生命周期的其他三个动作,在这种情况下是一个http请求 . 请记住,此模型适用于任何异步操作,因为必须有开头和结果标记成功或某些错误(失败)

    actions.js

    export function fetchSomethingRequest () {
      return {
        type: 'FETCH_SOMETHING_REQUEST'
      }
    }
    
    export function fetchSomethingSuccess (body) {
      return {
        type: 'FETCH_SOMETHING_SUCCESS',
        body: body
      }
    }
    
    export function fetchSomethingFailure (err) {
      return {
        type: 'FETCH_SOMETHING_FAILURE',
        err
      }
    }
    
    export function fetchSomething () {
      return function (dispatch) {
        dispatch(fetchSomethingRequest())
        return fetchSomething('http://example.com/').then(function (response) {
          if (response.status !== 200) {
            throw new Error('Bad response from server')
          } else {
            dispatch(fetchSomethingSuccess(response))
          }
        }).catch(function (reason) {
          dispatch(fetchSomethingFailure(reason))
        })
      }
    }
    

    你可能知道最后一个动作是redux thunk动作创建者 . 我们知道这是因为它是唯一返回函数的动作 .

    Creating our Mock Redux store

    在测试文件中,从redux-mock-store库导入configure store函数以创建我们的假存储 .

    import configureStore from 'redux-mock-store';
    

    此模拟存储将在数组中调度的操作将在您的测试中使用 .

    由于我们正在测试一个thunk动作创建者,我们的模拟商店需要在我们的测试中配置redux-thunk中间件,否则我们的商店将无法处理thunk动作创建者 . 或者换句话说,我们将无法调度函数而不是对象 .

    const middlewares = [ReduxThunk];
    const mockStore = configureStore(middlewares);
    

    Out mock store有一个store.getActions方法,当调用它时会给我们一个包含所有先前调度的动作的数组 .

    然后,我们进行测试断言,以比较调度到模拟存储的实际操作与我们预期的操作 .

    测试我们的摩卡动作创建者返回的承诺

    因此,在测试结束时,我们将thunk动作创建者发送到模拟商店 . 我们不能忘记返回此调度调用,以便在thunk动作创建者返回的promise被解析时,断言将在.then块中运行 .

    Working Tests

    如果您使用上述操作将此测试文件复制到您的应用程序中,请确保安装所有软件包并正确导入下面的测试文件中的操作,然后您将有一个测试redux thunk action creators的工作示例,以确保他们调度正确的行动 .

    import configureMockStore from 'redux-mock-store'
    import thunk from 'redux-thunk'
    import fetchMock from 'fetch-mock'  // You can use any http mocking library
    import expect from 'expect' // You can use any testing library
    
    import { fetchSomething } from './actions.js'
    
    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_SOMETHING_REQUEST', 
            'FETCH_SOMETHING_SUCCESS'
        ]
    
     // Mock the fetch() global to always return the same value for GET
     // requests to all URLs.
     fetchMock.get('*', { response: 200 })
    
        return store.dispatch(fetchSomething())
          .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_SOMETHING_REQUEST', 
            'FETCH_SOMETHING_FAILURE'
        ]
     // Mock the fetch() global to always return the same value for GET
     // requests to all URLs.
     fetchMock.get('*', { response: 404 })
    
        return store.dispatch(fetchSomething())
          .then(() => {
            const actualActions = store.getActions().map(action => action.type)
            expect(actualActions).toEqual(expectedActions)
         })
    
        fetchMock.restore()
      })
    })
    

    请记住,因为我们的Redux thunk动作创建者本身不是一个动作,只存在于发送进一步动作 .

    Much of our testing of thunk action creators will focus on making assertions on exactly what additional actions are dispatched under specific conditions.

    这些特定条件是异步操作的状态,其可以是超时的http请求或表示成功的200状态 .

    Common Gotcha when Testing Redux Thunks - 在行动创作者中没有回复承诺

    始终确保在为动作创建者使用promises时,您可以在动作创建者返回的函数内部使用promise .

    export function thunkActionCreator () {
              return function thatIsCalledByreduxThunkMiddleware() {
    
                // Ensure the function below is returned so that 
                // the promise returned is thenable in our tests
                return function returnsPromise()
                   .then(function (fulfilledResult) {
                    // do something here
                })
              }
         }
    

    因此,如果没有返回最后一个嵌套函数,那么当我们尝试异步调用该函数时,我们将得到错误:

    TypeError: Cannot read property 'then' of undefined - store.dispatch - returns undefined
    

    那是因为我们试图在.then条款中履行或拒绝承诺后作出断言 . 但是 . 然后才行,因为我们只能在承诺上打电话 . 由于我们忘记返回动作创建者中返回promise的最后一个嵌套函数,因此我们将在undefined上调用.then . 它未定义的原因是因为函数范围内没有return语句 .

    所以总是在动作创建器中返回函数,当调用return函数时 .

相关问题