Redux: Testando componentes conectados que têm componentes conectados aninhados dentro

Criado em 10 nov. 2016  ·  21Comentários  ·  Fonte: reduxjs/redux

Olá, isso é mais uma questão do que um problema.

Recentemente, comecei a usar React e Redux para um projeto e estou tendo alguns problemas com os testes, mas a documentação não parece fornecer uma solução conclusiva para o problema.

No momento, tenho um componente conectado para o qual gostaria de escrever alguns testes de unidade, no entanto, há um componente conectado aninhado no primeiro componente e pareço acabar sendo bloqueado pelos suportes ou estado (ou na maioria dos casos ambos) de o componente aninhado não está sendo definido.

Eu queria saber se existe uma maneira de criar um esboço de um componente para que eu possa me concentrar apenas no componente que estou tentando testar?

desde já, obrigado

question

Comentários muito úteis

A preocupação levantada aqui é quando o componente sendo testado renderiza outros componentes conectados, como filhos diretos ou em algum lugar abaixo da hierarquia. Se você fizer uma renderização completa por meio de mount() , então os componentes descendentes conectados irão absolutamente tentar acessar this.context.store , e não encontrá-lo. Portanto, as opções principais são garantir que os descendentes não sejam renderizados por meio de um teste superficial ou disponibilizando a loja no contexto.

Todos 21 comentários

Duas coisas:

1) este é o uso e provavelmente deve causar estouro de pilha.

2) parte da conveniência de usar o componente de ordem superior connect
é que você pode simplesmente escrever um teste de unidade para o componente unconnected e
confie no redux para lidar com a distribuição da loja como deveria

Se não está claro o que quero dizer, posso postar um exemplo.

Algumas idéias:

  • Uma maneira de focar em apenas um componente é usar a renderização "superficial", que não renderiza nenhum filho
  • Você também pode renderizar <Provider store={testStore}><ConnectedComponent /></Provider> em seus testes

Para sua informação, tenho links para vários artigos sobre testes React / Redux aqui, que podem ser úteis: https://github.com/markerikson/react-redux-links/blob/master/react-redux-testing. md .

E sim, como uma questão de uso, isso é realmente melhor perguntado em outro lugar.

Obrigado @markerikson. No momento, estou tentando o último método que você mencionou, mas também vou marcar sua lista de links :)

Ainda acho que se você quiser testar aquele componente isolado, é melhor fazer algo como

// Component.js
import React from 'react'

export const Component = ({ a, b, c }) => ( 
  // ...
)

// ...

export default connect(mapStateToProps, mapDispatchToProps)(Component)

então você pode simplesmente trazer o componente antes de ele ser conectado em seu teste, como

// Component.spec.js
import { Component } from './Component.js'
import { mount } from 'enzyme'

it('should mount without exploding', () => {
  mount(<Component a={1} b={2} c={3} />)
})

Se o que você realmente deseja testar é que as alterações em sua loja estão se propagando corretamente, então faz sentido renderizar Provider e isso é diferente, embora eu não tenha certeza se você obtém muito valor testando isso.

A preocupação levantada aqui é quando o componente sendo testado renderiza outros componentes conectados, como filhos diretos ou em algum lugar abaixo da hierarquia. Se você fizer uma renderização completa por meio de mount() , então os componentes descendentes conectados irão absolutamente tentar acessar this.context.store , e não encontrá-lo. Portanto, as opções principais são garantir que os descendentes não sejam renderizados por meio de um teste superficial ou disponibilizando a loja no contexto.

@markerikson Ah ok, acabei de reler e pensei que o OP estava tentando testar o componente interno em vez do externo. 👍

Obrigado pela ajuda até agora pessoal,

O principal problema que estou tendo no momento é que não consigo passar o estado para o componente conectado que está aninhado, portanto, a exibição não está sendo renderizada corretamente.

Zombar de estado em geral é algo com o qual tenho lutado bastante e não fui capaz de encontrar nada conclusivo sobre o assunto, então seria ótimo se alguém pudesse me dar uma ideia de como eu passaria um estado de zombaria para um componente conectado aninhado ou de ordem superior?

TIA

@StlthyLee : o que você quer dizer com "passar o estado"? Se você renderizar <Provider store={store}><ComponentWithConnectedDescendants /></Provider> em seu teste, isso deve resolver as coisas.

Esse é o problema, apenas fazer isso não é tratá-lo corretamente, o componente aninhado requer um parâmetro específico do estado do aplicativo que, no momento, não está sendo obtido por um motivo ou outro.

@StlthyLee provavelmente deve colocar um exemplo / gist / codepen / repo-link ou algo assim, levará menos tempo para detectar o problema, pois parece haver conflitos de terminologia aqui.

@Koleok espero que isso ajude a esclarecer

https://gist.github.com/StlthyLee/02e116d945867a77c382fa0105af1bf3

se não, pergunte, já que isso é algo que eu estou ansioso para descobrir, tendo passado os últimos 9 a 10 anos trabalhando no mundo Rails e tendo um bom entendimento de TDD a partir dessa perspectiva, agora estou tentando obter minha cabeça em torno de TDD no mundo React / Redux.

Então, acredito que cheguei ao fundo desse problema agora, não era tanto um problema de teste, mas mais uma cópia e um erro anterior feito por outro membro da equipe, acho que isso é parte integrante de ter que escrever o testes para ajustar o código em vez de minhas formas preferidas de TDD. Obrigado por todas as informações fornecidas aqui, foram extremamente valiosas

Apenas para oferecer outra opção que poderia funcionar para alguns casos de uso, recentemente me deparei com esse problema com um componente conectado aninhado. Em vez de fazer um armazenamento simulado, exportei as versões não conectadas de cada componente para teste de unidade sem armazenamento. Algo assim:

class TopLevelImpl extends React.PureComponent {
  // Implementation goes here
}

// Export here for unit testing.  The unit test imports privates and uses TopLevel
// with Enzyme's mount().
export const privates = {
  TopLevel: TopLevelImpl,
}

export const TopLevel = connect(mapStateToProps)(TopLevelImpl)

Fiz a mesma coisa para os componentes aninhados filhos, de modo que cada um pode ser testado independentemente.

Em seguida, peguei o componente conectado de nível superior e passei os componentes filhos conectados como adereços na função render() do aplicativo. No entanto, ao testar a unidade do componente de nível superior, em vez de passar os componentes conectados, apenas passo os componentes simulados para garantir que sejam renderizados no lugar certo. Você também pode passar os componentes não conectados.

Algo assim:

// Root render function
render() {
  return (
    <Provider store={store}>
      <TopLevel child1={<Child1 />} child2={<Child2 />} />
    </Provider>
  )
}

Na verdade, gosto da sua ideia. Mas eu faço algo assim em vez

const props = {
  child1: () => (<div />),
  child2: () => (<div />)
}
<TopLevel {...props} />

Eu acho que se você já testou child1 e child2, então provavelmente não há necessidade de tê-los em seu componente TopLevel.

Também passo uma função se precisar renderizar condicionalmente o componente.

Eu não sigo sua última frase, no entanto. O que o teste tem a ver com a passagem de componentes filhos para outro componente?

Bem. Por exemplo, tenho um componente externo conectado que usa um ou alguns componentes internos conectados . Neste caso, se eu quiser testar o componente externo, faço o seguinte

wrapper = mount(<ExternalComponent.WrappedComponent {...props} />)

No entanto, eu tenho um erro que meus componentes internos precisam de um estado para funcionar corretamente.

Invariant Violation: Could not find "store" in either the context or props

Então tentei passar o estado simulado, etc. Funcionou. No entanto, no meu caso, tenho que fazer muita inicialização para cada componente conectado, incluindo chamadas assíncronas, initialState etc e tive outros erros relacionados a isso.

Acho que será um exagero fazer toda a inicialização adequada para todos os componentes internos conectados se eu quiser apenas testar o componente externo.

Portanto, decidi retirar todos os componentes conectados e passá-los como acessórios para o componente externo conectado. E agora posso substituí-los por um div vazio quando quero testar.

Sim, essa foi apenas a motivação original para o padrão. Então, para modificar sua frase, você quis dizer "Acho que se você já testou child1 e child2, não há necessidade de passá-los para seu componente TopLevel ao testá-lo em unidade."

Acordado.

@StlthyLee , Infelizmente, enfrentei o mesmo problema ao tentar testar um componente que tem componentes conectados aninhados.

Uma solução que achei particularmente útil é conectar o componente aninhado ao redux store dependendo do ambiente de teste.

  • Quando os testes são executados, configure uma variável de ambiente NODE_ENV="test" . Você pode capturar essa variável e fazer com que o componente receba as propriedades padrão. Nesse caso, o componente não está conectado ao armazenamento e, ao testar o componente pai, nenhum problema aparece.
  • Em outros casos, faça o componente conectado. Este é o cenário padrão ao executar o componente no aplicativo.

a) Primeiro, você precisará de uma função auxiliar que determine se o ambiente é test :

export default function ifTestEnv(forTestEnv, forNonTestEnv) {
  const isTestEnv = process && process.env && process.env.NODE_ENV === 'test';
  const hoc = isTestEnv ? forTestEnv : forNonTestEnv;
  return function(component) {
    return hoc(component);
  };
}

b) Em seguida, defaultProps() e connect() devem ser aplicados condicionalmente, dependendo do ambiente:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import defaultProps from 'recompose/defaultProps';

import ifTestEnv from 'helpers/if_test_env';
import { fetch } from './actions';

class MyComponent extends Component {
   render() {
      const { propA, propB } = this.props;
      return <div>{propA} {probB}</div>
   }

   componentDidMount() {
      this.props.fetch();
   }
}

function mapStateToProps(state) {
  return {
    propA: state.valueA,
    propB: state.valueB
  };
}

export default ifTestEnv(
  defaultProps({
    propA: 'defaultA',
    propB: 'defaultB',
    fetch() {   }
  }),
  connect(mapStateToProps, { fetch })
)(MyComponent);

c) Certifique-se de que NODE_ENV seja "test" quando os testes forem executados:

  • Ao usar o mocha CLI, coloque um prefixo para definir o ambiente de teste: NODE_ENV='test' mocha app/**/*.test.jsx .
  • No caso de webpack, aplique um plugin:
plugins: [
    // plugins...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('test')
      },
    })
]

@markerikson Obrigado pelas duas sugestões. Tentei o segundo e envolvi meu componente para testar com um provedor. Neste caso, mapStateToProps funciona corretamente, mas não obtenho os resultados esperados com mapDispatchToProps . Aqui, o componente sendo testado renderiza outros componentes conectados.

Meu mapDispatchToProps se parece com

/* <strong i="10">@flow</strong> */
import { bindActionCreators } from 'redux';

import type { Dispatch } from './types';
import * as actions from './actions';

export default (dispatch: Dispatch, ownProps: Object): Object => ({
  actions: bindActionCreators(actions, dispatch),
});

aqui, o valor das ações retornadas é undefined .

store.js

import { applyMiddleware, compose, createStore } from 'redux';
import { autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';

import rootReducer from './reducers';
import { REHYDRATE } from 'redux-persist/constants';

const store = compose(autoRehydrate(), applyMiddleware(thunk, createActionBuffer(REHYDRATE)))(createStore)(rootReducer);

export default store;

Sei que esse é um tópico bastante antigo, mas achei um pouco incômodo e achei que essa solução pode ajudar alguém que se depara com esse tópico (como eu).

Como eu não estava realmente testando nada relacionado ao Redux, uma solução simples que encontrei foi simular a função de conexão para retornar apenas o componente inalterado que foi passado para ela.

Por exemplo, em seu aplicativo, você pode ter um componente:


export class ExampleApp extends Component {
  render() {
  }
}

export default connect(
  null,
  { someAction }
)(ExampleApp);

então, no início do seu arquivo de teste, você pode colocar uma função de conexão simulada para impedir o Redux de interagir com seu componente:

jest.mock("react-redux", () => {
  return {
    connect: (mapStateToProps, mapDispatchToProps) => (
      ReactComponent
    ) => ReactComponent
  };
});

Essa simulação precisará ser colocada em cada arquivo de teste que monta um filho conectado.

@scottbanyard como você passa acessórios para o componente filho?

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

cloudfroster picture cloudfroster  ·  3Comentários

parallelthought picture parallelthought  ·  3Comentários

vraa picture vraa  ·  3Comentários

ilearnio picture ilearnio  ·  3Comentários

ramakay picture ramakay  ·  3Comentários