2016-10-20 14 views
4

Я пытаюсь проверить свое действие с асинхронным вызовом. Я использую Thunk как мое промежуточное ПО. В приведенном ниже действии я только отправляю и обновляю хранилище, если сервер возвращает ответ OK.Тестирование асинхронных действий с redux thunk

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, чтобы высмеять реализацию 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() 
}) 

Посмотрев на это несколько больше, я считаю, что что-то не так с выборкой-насмешкой или не разрешение обещания так, что затем заявление выполнить или это полностью вырезая выборку. Когда я добавляю console.log для обоих операторов, ничего не выполняется.

Что я делаю неправильно в своих тестах?

ответ

1

Возможно, вы захотите рассмотреть использование 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 две отдельные функции, один для успеха и одну ошибку.

+0

Это во время испытания. Код работает нормально - я просто хочу знать, как я могу его протестировать. – Huy

+0

Ah ok sorry - Я считаю, что это потому, что в вашем. То есть у вас есть только одна функция - это будет вызвано только в случае успеха (ответ 200); вам нужно будет добавить другую функцию после нее, чтобы обработать состояние ошибки. Я уточню свой ответ, чтобы отразить это. –

+0

Я пробовал это, но это не проблема с фактическим кодом. Это проблема с тестом. Я не уверен, где это, но я не думаю, что правильно тестирую свое асинхронное действие. – Huy

12

Тестирование Асинхронный стуком Действия в Redux

Вы не вызывая setSubscribed перевождь-Thunk создателя действий в любом из тестов. Вместо этого вы определяете новое действие того же типа и пытаетесь отправить это на свой тест.

В обоих тестах синхронно отправляется следующее действие.

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

В этом действии ни один API не обращается с запросом.

Мы хотим, чтобы иметь возможность извлекать из внешнего API, а затем отправлять действие по успеху или сбою.

Поскольку мы отправляем действие в какой-то момент в будущем, нам нужно использовать созданный вами созданный создателем actionSubscribed thunk.

После краткого объяснения того, как работает redux-thunk, я объясню, как протестировать этого создателя действия thunk.

Действие против действий Творцов

Возможно, стоит объяснить, что творец действия это функция, которая при вызове возвращает объект действия.

Термин Действие относится к самому объекту. Для этого объекта действия единственным обязательным свойством является тип, который должен быть строкой.

Например, action creator.

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

Это просто функция, которая возвращает объект. Мы знаем, что этот объект является действием redux, потому что одно из его свойств называется типом.

Он создает toDos для добавления по требованию. Давайте сделаем новый todo, чтобы напомнить нам о собачьей ходьбе.

const walkDogAction = addTodo('walk the dog') 

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

На данный момент у нас есть объект, действие, которое было генерируется нашего действия создателя.

Теперь, если мы хотим отправить это действие нашим редукторам, чтобы обновить наш магазин, мы вызываем отправку с объектом действия в качестве аргумента.

store.dispatch(walkDogAction) 

Отлично.

Мы отправили объект, и он отправится прямо к редукторам и обновит наш магазин новым todo, напоминающим нам о том, чтобы идти собакой.

Как мы делаем более сложные действия? Что делать, если я хочу, чтобы мой создатель действия сделал что-то, что опирается на асинхронную операцию.

Синхронный против Asynchronous Redux действия

Что мы имеем в виду асинхронном (асинхронный) и синхронизации (синхронно)?

When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.

Ok, так что если я хочу спросить мою собаку принести что-нибудь? В этом случае есть три вещи, которые я забочусь о

  • , когда я попросил его принести объект
  • ли он принести что-то успешно?
  • не удалось ли получить объект? (т.е. вернулся ко мне, без палки, не возвращайся ко мне вообще после определенного количества времени)

Его, вероятно, трудно себе представить, как это может быть представлено в виде отдельного объекта, как наш addtodo action для ходьбы с собакой, которая просто состояла из типа и фрагмента текста.

Вместо действия, являющегося объектом, оно должно быть функцией. Почему функция? Функции можно использовать для отправки дальнейших действий.

Мы разделили большое всеобъемлющее действие извлечения на три небольших синхронных действия. Наш основной создатель действия при получении является асинхронным. Помните, что основной создатель действия не является самим действием, он существует только для отправки дальнейших действий.

Как работает создатель Thunk Action?

В сущности, создатели существительного действия - это создатели действий, которые возвращают функции вместо объектов. Добавляя сокращение в хранилище промежуточного программного обеспечения, эти специальные действия получат доступ к методам отправки и методам getState.

Here is the code inside Redux thunk that does this: 

    if (typeof action === 'function') { 
     return action(dispatch, getState, extraArgument); 
    } 

Функция setSubscribed является преобразователь действия создателя, как это следует подпись возвращающая функцию, которая принимает отправку в качестве аргумента.

Хорошо, поэтому наш создатель thunk action возвращает функцию. потому что эта функция будет вызвана промежуточным программным обеспечением и предоставит нам доступ к отправке и получит смысл состояния, мы можем отправить дальнейшие действия позднее.

Моделирование асинхронных операций с Actions

Позволяет записать свои действия. наш создатель action decux thunk отвечает за асинхронное рассылку трех других действий, которые представляют собой жизненный цикл aour async-действия, который в этом случае является 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)) 
    }) 
    } 
} 

Как вы, наверное, знаете, последнее действие создатель action. Мы это знаем, потому что это единственное действие, которое возвращает функцию.

Создание нашего Mock Redux магазина

В импорта тестового файла функция конфигурирования магазина из библиотеки перевождь-макет магазина, чтобы создать наш поддельный магазин.

import configureStore from 'redux-mock-store'; 

Этот макет хранит отправленные действия в массиве, который будет использоваться в ваших тестах.

Поскольку мы тестируем создателя thunk action, наш тестовый магазин должен быть сконфигурирован с промежуточным программным обеспечением redux-thunk в нашем тесте, иначе наш магазин не сможет обрабатывать создателей действия thunk. Или, другими словами, мы не сможем отправлять функции вместо объектов.

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

Затем мы делаем наши тестовые утверждения для сравнения фактических действий, которые должны были отправляться в магазин-макет, в сравнении с нашими ожидаемыми действиями.

Тестирование обещания возвращенного нашего стука действия создатель в Мокко

Так что в конце теста мы посылаем наши действия создателя преобразователя в макете магазина. Мы не должны забывать возвращать этот диспетчерский вызов, чтобы утверждения выполнялись в блоке .then, когда обещание, возвращаемое создателем действия thunk, разрешено.

Рабочие испытания

Если скопировать этот тестовый файл в приложение с вышеупомянутыми действиями, убедившись, чтобы установить все пакеты и импортировать действия в поле ниже тестового файла правильно, то вы будете иметь рабочий пример тестирования, чтобы гарантировать, что они отправят правильные действия.

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 не является самим действием и существует только для отправки дальнейших действий.

Большая часть наших испытаний создателей тёмного действия будет сосредоточена на принятии утверждений о том, какие дополнительные действия будут отправлены при определенных условиях.

Эти особые условия являются состоянием асинхронной операции, которая может быть тайм-аутом HTTP-запроса или статусом 200, представляющим успех.

Общие Попался, когда тестирование Redux санки - Не Возвратившись Promises в действии Творцов

Всегда убедитесь, что при использовании обещаний для создателей действий, которые вы вернуть обещание внутри функции, возвращаемой создателя действий.

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. Однако, тогда это не сработает, потому что мы можем позвонить только по обещанию. Поскольку мы забыли вернуть последнюю вложенную функцию в создателе действия, которая возвращает обещание, тогда мы будем звонить. Then on undefined. Причина, по которой она не определена, заключается в том, что в рамках функции нет оператора возврата.

Так что всегда возвращайте функции в создателях действий, которые при вызове return обещают.

+0

Я столкнулся с подобной проблемой. Я следил за вашим замечательным учебником и получал ошибку: TypeError: Невозможно прочитать свойство «then» undefined store.dispatch - возвращает undefined! Пожалуйста, помогите мне выяснить, почему – kurumkan

+1

Я обновил ответ, убедившись, что примеры работают правильно. Повторите попытку и сообщите мне, есть ли у вас какие-либо проблемы. – therewillbecode

+0

сейчас я решил проблему. Я забыл вернуть обещание в создателе действия: return axios.get (.... – kurumkan