Redux: Testen verbundener Komponenten, in denen verbundene Komponenten verschachtelt sind

Erstellt am 10. Nov. 2016  ·  21Kommentare  ·  Quelle: reduxjs/redux

Hallo, das ist eher eine Frage als ein Problem.

Ich habe kürzlich begonnen, React und Redux für ein Projekt zu verwenden, und ich habe einige Probleme beim Testen, aber die Dokumentation scheint keine endgültige Lösung für das Problem zu bieten.

Gegenwärtig habe ich eine verbundene Komponente, für die ich einige Komponententests schreiben möchte. Es gibt jedoch eine verbundene Komponente, die in der ersten Komponente verschachtelt ist, und ich scheine von den Requisiten oder dem Status (oder in den meisten Fällen beiden) von blockiert zu werden Die verschachtelte Komponente wird nicht festgelegt.

Ich habe mich gefragt, ob es eine Möglichkeit gibt, eine Komponente zu stubben, damit ich mich nur auf die Komponente konzentrieren kann, die ich testen möchte.

Danke im Voraus

question

Hilfreichster Kommentar

Hier besteht die Sorge, dass die getestete Komponente andere verbundene Komponenten entweder als direkte untergeordnete Komponenten oder irgendwo in der Hierarchie rendert. Wenn Sie ein vollständiges Rendern über mount() , versuchen die nachkommenden verbundenen Komponenten unbedingt, auf this.context.store zuzugreifen, und finden es nicht. Die Hauptoptionen sind also entweder sicherzustellen, dass die Nachkommen nicht durch einen flachen Test gerendert werden, oder den Speicher tatsächlich im Kontext verfügbar zu machen.

Alle 21 Kommentare

Zwei Dinge:

1) Dies ist eine Verwendung und sollte wahrscheinlich zum Stapelüberlauf führen.

2) Teil der Bequemlichkeit der Verwendung der Komponente connect höherer Ordnung
ist, dass Sie einfach einen Unit-Test für die unconnected -Komponente und schreiben können
Vertrauen Sie darauf, dass Redux den Laden so verlegt, wie er sollte

Wenn es unklar ist, was ich damit meine, kann ich ein Beispiel posten.

Ein paar Gedanken:

  • Eine Möglichkeit, sich nur auf die eine Komponente zu konzentrieren, ist die Verwendung eines "flachen" Renderings, bei dem keine untergeordneten Elemente gerendert werden
  • Sie können in Ihren Tests auch <Provider store={testStore}><ConnectedComponent /></Provider> rendern

Zu Ihrer Information, ich habe hier Links zu einer Reihe von Artikeln über React / Redux-Tests, die Sie möglicherweise hilfreich finden: https://github.com/markerikson/react-redux-links/blob/master/react-redux-testing. md .

Und ja, als Verwendungsfrage wird dies an anderer Stelle wirklich besser gestellt.

Danke @markerikson Ich versuche gerade die letztere Methode, die Sie erwähnt haben, werde aber auch Ihre Liste der Links mit einem Lesezeichen versehen :)

Ich denke immer noch, wenn Sie diese einmalige Komponente isoliert testen möchten, ist es besser, wenn Sie so etwas tun

// Component.js
import React from 'react'

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

// ...

export default connect(mapStateToProps, mapDispatchToProps)(Component)

dann können Sie einfach die Komponente einbringen, bevor sie in Ihrem Test wie verbunden wird

// 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} />)
})

Wenn Sie wirklich testen möchten, dass Änderungen an Ihrem Geschäft korrekt übertragen werden, ist es sinnvoll, die Provider zu rendern, und das ist anders, obwohl ich nicht sicher bin, ob Sie durch das Testen viel Wert erhalten.

Hier besteht die Sorge, dass die getestete Komponente andere verbundene Komponenten entweder als direkte untergeordnete Komponenten oder irgendwo in der Hierarchie rendert. Wenn Sie ein vollständiges Rendern über mount() , versuchen die nachkommenden verbundenen Komponenten unbedingt, auf this.context.store zuzugreifen, und finden es nicht. Die Hauptoptionen sind also entweder sicherzustellen, dass die Nachkommen nicht durch einen flachen Test gerendert werden, oder den Speicher tatsächlich im Kontext verfügbar zu machen.

@markerikson Ah ok, ich habe gerade noch einmal gelesen, dass das OP versucht hat, die innere Komponente und nicht die äußere zu testen. 👍

Vielen Dank für Ihre Hilfe, Jungs,

Das Hauptproblem, das ich im Moment habe, ist, dass ich den Status scheinbar nicht an die verbundene Komponente übergeben kann, die darin verschachtelt ist, sodass die Ansicht nicht korrekt gerendert wird.

Der Verspottungszustand im Allgemeinen ist etwas, mit dem ich eigentlich ziemlich zu kämpfen hatte und das ich zu diesem Thema nicht schlüssig finden konnte. Es wäre also großartig, wenn mir jemand eine Vorstellung davon geben könnte, wie ich einen verspotteten Zustand an einen weitergeben würde verschachtelte verbundene oder höherwertige Komponente?

TIA

@StlthyLee : Was meinst du mit "pass the state"? Wenn Sie in Ihrem Test <Provider store={store}><ComponentWithConnectedDescendants /></Provider> rendern, sollte dies die Dinge regeln.

Das ist das Problem, nur dass dies nicht richtig behandelt wird. Die darin verschachtelte Komponente erfordert einen bestimmten Parameter aus dem Anwendungsstatus, den sie derzeit aus dem einen oder anderen Grund nicht erhält.

@StlthyLee sollte wahrscheinlich ein Beispiel / gist / codepen / repo-link oder etwas anderes einfügen, es wird weniger Zeit brauchen, um das Problem zu erkennen, da es hier Terminologiekonflikte zu geben scheint.

@Koleok hoffentlich

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

Wenn nicht, fragen Sie bitte, da dies etwas ist, dem ich auf den Grund gehen möchte. Nachdem ich die letzten 9 bis 10 Jahre in der Rails-Welt gearbeitet habe und TDD aus dieser Perspektive gut verstanden habe, versuche ich jetzt, dies zu erreichen Mein Kopf um TDD in der React / Redux-Welt.

Ich glaube, ich bin jetzt diesem Problem auf den Grund gegangen. Es war nicht so sehr ein Testproblem, sondern eher eine Kopie und ein früherer Fehler eines anderen Teammitglieds. Ich denke, dies ist ein wesentlicher Bestandteil des Schreibens Tests, die eher dem Code als meinen bevorzugten TDD-Methoden entsprechen. Vielen Dank für alle Informationen, die hier gegeben wurden. Es war äußerst wertvoll

Um eine weitere Option anzubieten, die für einige Anwendungsfälle geeignet ist, bin ich kürzlich auf dieses Problem mit einer verschachtelten verbundenen Komponente gestoßen. Anstatt einen Scheinspeicher zu erstellen, habe ich die nicht verbundenen Versionen jeder Komponente exportiert, um sie ohne Speicher zu testen. Etwas wie das:

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)

Ich habe das Gleiche für die untergeordneten verschachtelten Komponenten getan, sodass jede unabhängig getestet werden kann.

Dann nahm ich die verbundene Komponente der obersten Ebene und übergab die untergeordneten verbundenen Komponenten als Requisiten in der render() -Funktion der App. Beim Komponententest der Komponente der obersten Ebene übergebe ich jedoch nur Scheinkomponenten, anstatt sicherzustellen, dass sie an der richtigen Stelle gerendert werden. Sie können jedoch auch die nicht verbundenen Komponenten übergeben.

Etwas wie das:

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

Ich mag deine Idee wirklich. Aber ich mache stattdessen so etwas

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

Ich denke, wenn Sie bereits child1 und child2 getestet haben, ist es wahrscheinlich nicht erforderlich, sie in Ihrer TopLevel-Komponente zu haben.

Ich übergebe auch eine Funktion, wenn ich die Komponente bedingt rendern muss.

Ich folge deinem letzten Satz allerdings nicht. Was hat Testen mit der Übergabe untergeordneter Komponenten an eine andere Komponente zu tun?

Gut. Zum Beispiel habe ich eine extern verbundene Komponente, die eine oder wenige intern verbundene Komponenten verwendet . In diesem Fall mache ich so etwas, wenn ich die externe Komponente testen möchte

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

Ich habe jedoch den Fehler, dass meine internen Komponenten den Status benötigen , um ordnungsgemäß ausgeführt zu werden.

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

Dann habe ich versucht, den Scheinzustand usw. zu bestehen. Es hat funktioniert. In meinem Fall muss ich jedoch viel Initialisierung für jede verbundene Komponente durchführen, einschließlich asynchroner Aufrufe, initialState usw., und ich hatte andere Fehler im Zusammenhang damit.

Ich denke, dass dies ein Overkill sein wird, um alle ordnungsgemäßen Initialisierungen für alle intern verbundenen Komponenten durchzuführen. Wenn ich nur die externe Komponente testen möchte.

Deshalb habe ich beschlossen, alle angeschlossenen Komponenten zu verschieben und als Requisiten an die extern angeschlossenen Komponenten weiterzugeben. Und jetzt kann ich sie durch leere Divs ersetzen, wenn ich testen möchte.

Oh ja, das war nur die ursprüngliche Motivation für das Muster. Um Ihren Satz zu ändern, meinten Sie: "Ich denke, wenn Sie child1 und child2 bereits getestet haben, müssen Sie sie beim Unit-Test nicht an Ihre TopLevel-Komponente übergeben."

Einverstanden.

@StlthyLee , leider hatte ich das gleiche Problem beim Versuch, eine Komponente zu testen, die verbundene Komponenten verschachtelt hat.

Eine Lösung, die ich als besonders nützlich empfand, besteht darin, die verschachtelte Komponente abhängig von der Testumgebung mit dem Redux-Speicher zu verbinden.

  • Wenn Tests ausgeführt werden, richten Sie eine Umgebungsvariable NODE_ENV="test" . Sie können diese Variable abfangen und die Komponente dazu bringen, Standardeigenschaften zu erhalten. In diesem Fall ist die Komponente nicht mit dem Speicher verbunden, und beim Testen der übergeordneten Komponente treten keine Probleme auf.
  • In anderen Fällen wird die Komponente angeschlossen. Dies ist das Standardszenario beim Ausführen der Komponente in der Anwendung.

a) Zunächst benötigen Sie eine Hilfsfunktion, die bestimmt, ob die Umgebung 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) Als nächstes sollten defaultProps() und connect() abhängig von der Umgebung bedingt angewendet werden:

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) Stellen Sie sicher, dass NODE_ENV "test" wenn Tests ausgeführt werden:

  • Geben Sie bei Verwendung der Mokka-CLI ein Präfix ein, um die Testumgebung festzulegen: NODE_ENV='test' mocha app/**/*.test.jsx .
  • Wenden Sie im Falle eines Webpacks ein Plugin an:
plugins: [
    // plugins...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('test')
      },
    })
]

@markerikson Danke für die beiden Vorschläge. Ich habe den zweiten ausprobiert und meine Komponente verpackt, um sie mit einem Anbieter zu testen. In diesem Fall funktioniert mapStateToProps korrekt, aber ich erhalte mit mapDispatchToProps keine erwarteten Ergebnisse. Hier rendert die zu testende Komponente andere verbundene Komponenten.

Mein mapDispatchToProps sieht ungefähr so ​​aus

/* <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),
});

Hier beträgt der Wert der zurückgegebenen Aktionen 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;

Mir ist klar, dass dies ein ziemlich alter Thread ist, aber ich fand, dass dies ein lästiges Problem ist, und dachte, diese Lösung könnte jemandem helfen, der über diesen Thread stolpert (wie ich).

Da ich eigentlich nichts mit Redux zu tun hatte, bestand eine einfache Lösung darin, die Verbindungsfunktion zu verspotten, um nur die unveränderte Komponente zurückzugeben, die an sie übergeben wurde.

ZB in Ihrer Anwendung können Sie eine Komponente haben:


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

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

Dann können Sie am Anfang Ihrer Testdatei eine verspottete Verbindungsfunktion platzieren, um die Interaktion von Redux mit Ihrer Komponente zu stoppen:

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

Diese Verspottung muss in jede Testdatei eingefügt werden, in der ein verbundenes Kind bereitgestellt wird.

@scottbanyard Wie untergeordnete Komponente?

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen