response-redux 4.0: тестирование mapStateToProps и mapDispatchToProps? - PullRequest
0 голосов
/ 21 декабря 2018

response-redux 4.0 теперь использует API контекста, и это здорово!Однако, когда я пытаюсь обновить мой проект, все работает, но мои модульные тесты начинают проваливаться.

Для контекста, это очень скучный «шаблонный» проект, над которым я работаю ради забавы.Для демонстрационных целей это в основном приложение для дел (все мы любим задачи, верно?).

Теперь для своих контейнеров я провожу некоторое модульное тестирование, но очень простое.По сути, я хочу убедиться, что и mapStateToProps, и mapDispatchToProps получают то, что они должны получать.Мне все равно, как, просто они находятся внутри.

Вот мой компонент "ConnectedToDos":

import { injectIntl } from 'react-intl'
import { connect } from 'react-redux'
import { createStructuredSelector } from 'reselect'

import {
  addTodo,
  completeTodo,
  deleteTodo,
  loadTodos,
  makeGetTodos,
} from './duck'

import ToDos from './view'

const mapStateToProps = createStructuredSelector({
  todos: makeGetTodos(),
})

const mapDispatchToProps = {
  handleComplete: completeTodo,
  handleSubmit: addTodo,
  handleDelete: deleteTodo,
  requestTodos: loadTodos,
}

export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(ToDos))

Теперь, чтобы проверить это, я использовал следующее:

import React from 'react'
import PropTypes from 'prop-types'
import configureStore from 'redux-mock-store'
import { shallow, mount } from 'enzyme'
import { fromJS } from 'immutable'
import { MemoryRouter } from 'react-router-dom'
import { IntlProvider } from 'react-intl'

import ConnectedToDos from 'modules/ToDos'

describe('Connected <ToDos />', () => {
  const initialState = fromJS({
    resources: {
      todos: [
        { title: 'A todo', description: 'Do me!', id: '1', done: false },
        { title: 'A todo', description: 'Do me!', id: '2', done: true },
        { title: 'A todo', description: 'Do me!', id: '3', done: false },
        { title: 'A todo', description: 'Do me!', id: '4', done: true },
      ],
    },
  })

  let mockStore
  let store

  beforeAll(() => {
    mockStore = configureStore([])
    store = mockStore(initialState)
  })

  it('props should match mapStateToProps', () => {
    const wrapper = shallow(<ConnectedToDos store={store} />)

    expect(wrapper.prop('todos')).toEqual(initialState.getIn(['resources', 'todos']))
  })

  it('props should match mapDispatchToProps', () => {
    const wrapper = mount(
      <IntlProvider locale="en" messages={{ en: {} }}>
        <MemoryRouter>
          <ConnectedToDos />
        </MemoryRouter>
      </IntlProvider>,
      {
        context: { store },
        childContextTypes: { store: PropTypes.object.isRequired },
      },
    )

    wrapper.find('input[type="checkbox"]').first().simulate('change')
    wrapper.find('form').simulate('submit')
    wrapper.find('Icon[name="DELETE"]').first().simulate('click')

    const actions = store.getActions()
    const todosActions = actions.filter(action => action.type.includes('/todos/'))

    expect(todosActions).toHaveLength(4)
  })
})

Однако теперь выдается хорошее предупреждение:

Инвариантное нарушение: Не удалось найти «store» в контексте «Connect (InjectIntl ​​(ToDos)))».Либо оберните корневой компонент в <Provider>, либо передайте пользовательский поставщик контекста React в <Provider> и соответствующий потребитель контекста React в Connect(InjectIntl(ToDos)) в параметрах соединения.

Cool.Но теперь я задаюсь вопросом, как лучше всего протестировать мои контейнеры?

Я пробовал множество способов, последний из которых выглядит следующим образом:

  it('props should match mapStateToProps', () => {
    const wrapper = shallow(<ConnectedToDos />, { context: { store } })

    expect(wrapper.prop('todos')).toEqual(initialState.getIn(['resources', 'todos']))
  })

Но это не удается.Кажется, что что бы я ни делал, wrapper.context() - это всегда пустой объект.

Я не могу скопировать и вставить ВСЕ свой код здесь, но у меня есть репозиторий, который вы можете клонировать и поиграть с собой:https://github.com/enrique-ramirez/react-redux-boilerplate

Тест, который не прошел, включен src/modules/ToDos/tests/index.test.js.

TL; DR Как я могу проверить свои mapStateToProps и mapDispatchToProps, используя новый реактивный редукс?

1 Ответ

0 голосов
/ 14 января 2019

Решение Шубхам Хатри выше не сработало для меня, поэтому мне пришлось потратить некоторое время на это в выходные.Вот что у меня получилось:

Для mapStateToProps я в основном монтирую его в рамках необходимых провайдеров (для меня это провайдер магазина, наряду с IntlProvider и MemoryRouter; но единственный релевантный - это избыточный <Provider>).Затем я нахожу представление (не связанный компонент, а обычный) и извлекаю все его реквизиты в переменную.После этого я могу проверить, соответствует ли каждое свойство тому, что есть в моем магазине, и это именно то, что я хочу проверить (правильно ли сопоставлены реквизиты из магазина):

  it('props should match mapStateToProps', () => {
    const wrapper = mount(
      <Provider store={store}>
        <IntlProvider locale="en" messages={{ en: {} }}>
          <MemoryRouter>
            <ConnectedToDos />
          </MemoryRouter>
        </IntlProvider>
      </Provider>,
      {},
    )
    const props = wrapper.find(ToDos).props()

    expect(props.todos).toEqual(initialState.getIn(['resources', 'todos']))
  })

Для mapDispatchToProps это довольнопочти так же, как это было, но вместо того, чтобы передать хранилище в контексте, мне пришлось обернуть все в <Provider> в избыточном, иначе это не сработало бы:

  it('props should match mapDispatchToProps', () => {
    const wrapper = mount(
      <Provider store={store}>
        <IntlProvider locale="en" messages={{ en: {} }}>
          <MemoryRouter>
            <ConnectedToDos />
          </MemoryRouter>
        </IntlProvider>
      </Provider>,
      {},
    )

    wrapper.find('input[type="checkbox"]').first().simulate('change')
    wrapper.find('form').simulate('submit')
    wrapper.find('Icon[name="DELETE"]').first().simulate('click')

    const actions = store.getActions()
    const todosActions = actions.filter(action => action.type.includes('/todos/'))

    expect(todosActions).toHaveLength(4)
  })

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

...