Redux: Kann nicht auf Container verweisen, die in einem Provider verpackt sind oder mit Enzyme verbunden sind

Erstellt am 20. März 2016  ·  51Kommentare  ·  Quelle: reduxjs/redux

Ich kann anscheinend auf nichts verweisen, das in ein <Provider> und ein connect gewickelt ist

// test
let component = shallow(<Provider store={store}><ContainerComponent /></Provider>);
component.find('#abc'); // returns null

let component = shallow(<Provider store={store}><div id="abc"></div></Provider>);
component.find('#abc'); // returns the div node

// ContainerComponent
const Component = ({...}) => (<div id="abc"></div>);
export default connect(..., ...)(Component);

Ich folgte: https://github.com/reactjs/redux/issues/1481 zu den Beispielen, wo die Tests mit Enzym geschrieben wurden, aber die Container werden in diesen nie getestet. Ich weiß also nicht, ob ich Smart Container testen soll/kann?

@fshowalter Irgendwelche Ideen?

Hilfreichster Kommentar

Auch wenn es nicht direkt mit Redux zu tun hat. Ich habe dies gelöst, indem ich erneut shallow() für den Container aufgerufen habe. Es rendert die innere Komponente mit dem darin übergebenen Speicherzustand.

Beispiel:

import React from 'react';
import {expect} from 'chai';
import {shallow} from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import events from 'events';

const mockStore = configureMockStore([ thunk ]);
const storeStateMock = {
  myReducer:{
    someState: 'ABC'
  }
};

let store;
let component;
describe('tests for MyContainerComponent', () => {
  beforeEach(() => {
    store = mockStore(storeStateMock);
    component = shallow(<MyContainerComponent store={store} />).shallow();
  });

  it('renders container', () => {
    expect(component.find('div.somediv')).to.have.length.of(1);
  });
  ...
});

Hoffe das hilft

Alle 51 Kommentare

Weder connect() noch Provider sind Teil dieser Bibliothek. Es ist einfacher, solche Probleme zu diskutieren und zu verfolgen, wenn sie stattdessen im entsprechenden Repository abgelegt werden: https://github.com/reactjs/react-redux.

Ich finde das eigentlich sinnvoll. Da Sie flach rendern, wird nur die Provider-Komponente gerendert - Ihre ContainerComponent wird einfach in ihrer Objektausgabeform belassen oder was auch immer das flache Rendering erzeugt.

Zwei Gedanken hier:

Beachten Sie zunächst, dass die von connect() generierte Wrapper-Komponente tatsächlich nach props.store sucht, bevor sie nach context.store sucht. Wenn Sie also tatsächlich das Gefühl haben, dass Sie eine verbundene Komponente testen müssen , sollten Sie in der Lage sein, <ConnectedComponent store={myTestStore} /> zu rendern, ohne sich um Provider oder Kontext oder irgendetwas kümmern zu müssen.

Die zweite Frage ist, ob Sie sich wirklich Sorgen machen müssen, die vollständig angeschlossene Komponente tatsächlich zu testen. Das Argument, das ich gesehen habe, ist, dass Sie, wenn Sie Ihre "einfache" Komponente mit bestimmten Requisiten testen können, und Sie Ihre mapStateToProps -Implementierung testen können, sicher davon ausgehen können, dass React-Redux sie korrekt zusammensetzt, und Sie müssen die verbundene Version nicht selbst testen.

@gaearon du hast recht, sorry. Wusste nicht, ob ich das in React oder Enzym Repo erhöhen sollte.

@markerikson Der Grund für das Testen der intelligenten Komponente ist für mapToDispatchProps , wo ich sicherstellen wollte, dass der richtige Dispatcher von der umschlossenen Komponente aufgerufen wurde. Nur den Speicher an ConnectedComponent bedeutet, dass ich weder die Zustandszuordnung noch die Callback-Funktionen testen werde.

Ich werde darüber mehr nachdenken und ein Problem im entsprechenden Repo ansprechen, wenn ich es brauche. Danke für die Hilfe.

@mordra : Wenn Sie sagen "der richtige Dispatcher wurde gerufen", meinen Sie tatsächlich "den richtigen Aktionsersteller"?

Sie könnten so etwas tun (Syntax ist wahrscheinlich deaktiviert, aber Sie sollten die Idee verstehen):

let actionSpy = sinon.spy();

let wrapper = shallow(<PlainComponent someActionProp={actionSpy} />);
wrapper.find(".triggerActionButton").simulate("click");
expect(actionSpy.calledOnce).to.be.true;
expect(actionSpy.calledWith({type : ACTION_I_WAS_EXPECTING)).to.be.true;

Mit anderen Worten, rendern Sie Ihre einfache Komponente, übergeben Sie Spies für die Requisiten, die von mapDispatch zurückgegebene Aktionen gewesen wären, und lösen Sie das Verhalten aus, das die Komponente benötigt, damit sie diese Aktionen aufruft.

Außerdem, gemäß meinem früheren Kommentar: Sie sollten in der Lage sein, Ihre mapStateToProps und mapDispatchToProps separat zu testen, und sich sicher fühlen, dass React-Redux sie entsprechend aufruft, ohne versuchen zu müssen, die verbundene Version zu testen selbst zu überprüfen, ob dies der Fall ist.

@markerikson Ich kann nicht tun, was Sie vorschlagen, weil mein PlainComponent nichts über Aktionen oder Aktionsersteller weiß. Ich weiß nicht, ob das der richtige Weg ist, aber wenn Sie sich Folgendes ansehen:
https://github.com/mordra/cotwmtor/blob/master/client/charCreation/charCreation.jsx
Sie werden sehen, dass mein mapDispatchToProps die gesamte Logik enthält:

const mapDispatchToProps = (dispatch) => {
  return {
    onCompleted      : (player) => {
      Meteor.call('newGame', player, function (data) {
        console.log('new game return: ' + data);
      });
      dispatch(routeActions.push('/game'));
      dispatch({type: "INIT_GAME", map: generateAreas(), buildings: generateBuildings(dispatch)});
    },
    onCancelled      : () => dispatch(routeActions.push('/')),
    onChangeName     : input => dispatch(actions.changeName(input)),
    onChangeGender   : gender => dispatch(actions.setGender(gender)),
    onSetDifficulty  : lvl => dispatch(actions.setDifficulty(lvl)),
    onChangeAttribute: (attr, val) => dispatch(actions.setAttribute(attr, val))
  }
};

Wenn ich also die an die PlainComponent übergebenen Requisiten ausspionieren würde, könnte ich nicht testen, ob die richtigen Aktionen aufgerufen werden, sondern nur die an die Aktionsersteller übergebenen Parameter sind korrekt.
Ich müsste dann die Komponente connect separat testen.

Ah. Das hilft mir, mehr zu verstehen.

Also mit der Einschränkung, dass ich eigentlich sehr wenig praktische Erfahrung mit dem Schreiben von Tests habe, ein paar Gedanken:

  • Was Sie dort tatsächlich haben, sind Aktionsersteller, sie werden nur nicht separat definiert, weil Sie Zugriff auf dispatch haben möchten. Das ist an und für sich nicht sehr einfach zu testen. Wahrscheinlich möchten Sie auch ihr Verhalten testen können, und dazu möchten Sie sie als ihre eigenen Funktionen außerhalb von mapDispatch definieren.
  • All dies sieht nach sehr guten Kandidaten für die Verwendung mit redux-thunk aus, was es sicherlich einfacher machen würde, diese Funktionen selbst zu definieren und ihnen den Zugriff auf dispatch zu ermöglichen.
  • Ihre PlainComponent _weiß_ über "Aktionen" oder zumindest Callbacks in diesem Fall Bescheid, indem Sie diese weitergeben und irgendwo in Ihrer Komponente this.props.onSetDifficulty("HARD") oder so etwas ausführen. Als ich vorschlug, Spione für Aktionen einzusetzen, war dies die Art von Dingen, die ich vorschlug, zu ersetzen. Wenn Sie also einen Spion für onSetDifficulty übergeben, können Sie überprüfen, ob Ihre Komponente ihn aufgerufen hat, und einen akzeptablen Wert für den Schwierigkeitsgrad übergeben.

Letztendlich denke ich, dass Sie in der Lage sein sollten, die Dinge viel besser testbar zu machen, indem Sie die in mapDispatch definierten Funktionen nehmen und sie separat definieren. Dann müssen Sie sich keine Sorgen darüber machen, dass mapDispatch angeschlossen sein müssen, um Ihre Komponente richtig zu testen, und können sich darauf konzentrieren, ob die Komponente gerade einen bestimmten Prop-Callback mit den richtigen Argumenten oder so aufgerufen hat oder nicht.

Auch aus Sicht der Komponente: Letztendlich kümmert es sich nicht wirklich darum, ob {action: CHANGE_NAME, name : "Fred"} versendet wurde. Alles, was es weiß, ist, dass es this.props.onChangeName("Fred") aufgerufen hat, und _das_ ist, was Sie versuchen sollten zu testen - dass es einen Prop-Callback auf die richtige Weise aufgerufen hat.

@mordra in deinem Fall würde ich mapDispatchToProps exportieren und es isoliert testen. Sie können überprüfen, ob alle Eigenschaftsnamen korrekt sind, und einen Spion zum Versand übergeben, um Ihre Aktionsersteller zu testen.

Allerdings neige ich dazu, auf mapDispatchToProps zugunsten von mapActionCreatorsToProps zu verzichten, das bereits gebildete Aktionsersteller abbildet, die ich leicht isoliert testen kann. In diesem Fall teste ich einfach, ob alle meine Requisiten so definiert sind, dass sie vor Tippfehlern beim Import schützen.

Beachten Sie schließlich, dass Sie normales (nicht flaches) Rendering verwenden können. Dafür benötigen Sie entweder jsdom oder einen echten Browser, aber dann können Sie jede Ebene tief rendern.

Wusste nicht, ob ich das in React oder Enzym Repo erhöhen sollte

Entschuldigung, ich meinte nicht React oder Enzyme Repos. Ich meinte React Redux , die Bibliothek, die Sie verwenden.

Danke für die Hilfe Jungs, das sind eine Menge Informationen für mich zu gehen und zu verdauen. @markerikson @fshowalter Ich habe mich noch etwas umgesehen und werde Ihren Vorschlag annehmen, mapState/Dispatch zu exportieren und separat zu testen. Es ist sinnvoll, die Rückrufe zu testen, die die erwarteten Aktionen hervorrufen.

@gaearon Stateless-Komponente wird nicht gerendert, ohne die Probleme zu überfliegen, es scheint Probleme beim Rendern von zustandsbehafteten Komponenten mit verschachtelten zustandslosen Komponenten zu geben, daher habe ich mir eine mentale Notiz gemacht, diesen Pfad vorerst zu vermeiden.

Ich bin mir nicht sicher, auf welche Probleme Sie sich beziehen. Sie können problemlos flaches Rendering mit funktionalen Komponenten verwenden. Flaches Rendering funktioniert jedoch nur eine Ebene tief (egal wie die Komponente definiert ist). Dies ist sein Hauptmerkmal – es wiederholt sich nicht, sodass Komponententests unabhängig bleiben. Wenn Sie spezifische Probleme mit flachem Rendering haben, die Sie reproduzieren können, melden Sie bitte Fehler im React-Repo. Danke!

@gaearon : also zur Verdeutlichung, wenn ich das mache:

let wrapper = shallow(<A><B /></A>);

wird B gerendert oder nicht? Denn ich denke, das war die ursprüngliche Frage - der Versuch, ein <Provider> um eine verbundene Komponente zu rendern, um die Verbindung zu testen.

Auch wenn es nicht direkt mit Redux zu tun hat. Ich habe dies gelöst, indem ich erneut shallow() für den Container aufgerufen habe. Es rendert die innere Komponente mit dem darin übergebenen Speicherzustand.

Beispiel:

import React from 'react';
import {expect} from 'chai';
import {shallow} from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import events from 'events';

const mockStore = configureMockStore([ thunk ]);
const storeStateMock = {
  myReducer:{
    someState: 'ABC'
  }
};

let store;
let component;
describe('tests for MyContainerComponent', () => {
  beforeEach(() => {
    store = mockStore(storeStateMock);
    component = shallow(<MyContainerComponent store={store} />).shallow();
  });

  it('renders container', () => {
    expect(component.find('div.somediv')).to.have.length.of(1);
  });
  ...
});

Hoffe das hilft

Sollten wir dies vielleicht durch ein benutzerdefiniertes Modul zugänglicher machen?

Bearbeiten: So können wir connect als eine Methode für Enzyme deklarieren, z. B. component = shallowWithConnect()

@ev1stensberg Das ist eine tolle Idee. Haben wir entschieden, ob dies der Ansatz ist, und/oder hat die Arbeit daran begonnen? Wenn nicht, würde ich gerne einen Beitrag leisten.

Für alle, die in Zukunft auf dieses Problem stoßen, hier ist der Ansatz, der für mich funktioniert: Testen Sie einfach die einfache Komponente selbst. Exportieren Sie sowohl die Komponentendefinition als benannten Export als auch die verbundene Komponente (zur Verwendung in Ihrer App) als Standardexport. Zurück zum Code aus der ursprünglichen Frage:

// test
let component = shallow(<Provider store={store}><ContainerComponent /></Provider>);
component.find('#abc'); // returns null

let component = shallow(<Provider store={store}><div id="abc"></div></Provider>);
component.find('#abc'); // returns the div node

// ContainerComponent
export const Component = ({...}) => (<div id="abc"></div>); // I EXPORT THIS, TOO, JUST FOR TESTING
export default connect(..., ...)(Component);

Dann tu es einfach

const component = shallow(<Component {...props} />)
// test component

Und wie andere oben angemerkt haben, sollten Sie, wenn Sie auch eine Abdeckung für die Funktionen haben, die Sie an Connect übergeben, eine vollständige Abdeckung für Ihre Komponente als Ganzes haben.

Nun, das erste, was hier zu beachten ist, ist die Philosophie des :

  1. Kommunizieren Sie den Zustand aus dem Geschäft an a
  2. Ändern Sie den Status, indem Sie Aktionen basierend auf dem verteilen Veranstaltungen.

Wenn ich in Ordnung bin, müssen Sie nur 2 Dinge testen:

  1. erhält Ihre Komponente die richtigen Requisiten, die aus dem Status generiert wurden (könnte über Selektoren erfolgen)?
  2. Versendet Ihre Komponente die richtigen Aktionen?

Das ist also mein Ansatz

import React from 'react';
import { shallow } from 'enzyme';
import { fromJS } from 'immutable';
import { createStore } from 'redux';
// this is the <Map /> container
import Map from '../index';
// this is an action handled by a reducer
import { mSetRegion } from '../reducer';
// this is an action handled by a saga
import { sNewChan } from '../sagas';
// this is the component wrapped by the <Map /> container
import MapComponent from '../../../components/Map';

const region = {
  latitude: 20.1449858,
  longitude: -16.8884463,
  latitudeDelta: 0.0222,
  longitudeDelta: 0.0321,
};
const otherRegion = {
  latitude: 21.1449858,
  longitude: -12.8884463,
  latitudeDelta: 1.0222,
  longitudeDelta: 2.0321,
};
const coordinate = {
  latitude: 31.788,
  longitude: -102.43,
};
const marker1 = {
  coordinate,
};
const markers = [marker1];
const mapState = fromJS({
  region,
  markers,
});
const initialState = {
  map: mapState,
};
// we are not testing the reducer, so it's ok to have a do-nothing reducer
const reducer = state => state;
// just a mock
const dispatch = jest.fn();
// this is how i mock the dispatch method
const store = {
  ...createStore(reducer, initialState),
  dispatch,
};
const wrapper = shallow(
  <Map store={store} />,
);
const component = wrapper.find(MapComponent);

describe('<Map />', () => {
  it('should render', () => {
    expect(wrapper).toBeTruthy();
  });

  it('should pass the region as prop', () => {
    expect(component.props().region).toEqual(region);
  });

  it('should pass the markers as prop', () => {
    expect(component.props().markers).toEqual(markers);
  });
  // a signal is an action wich will be handled by a saga
  it('should dispatch an newChan signal', () => {
    component.simulate('longPress', { nativeEvent: { coordinate } });
    expect(dispatch.mock.calls.length).toBe(1);
    expect(dispatch.mock.calls[0][0]).toEqual(sNewChan(coordinate));
  });
  // a message is an action wich will be handled by a reducer
  it('should dispatch a setRegion message', () => {
    component.simulate('regionChange', otherRegion);
    expect(dispatch.mock.calls.length).toBe(2);
    expect(dispatch.mock.calls[1][0]).toEqual(mSetRegion(otherRegion));
  });
});

@markerikson was meintest du mit:

Sie können Ihre MapStateToProps-Implementierung testen

Angenommen, Sie exportieren den verbundenen Container über einen ES6-Export. Sie hätten keinen Zugriff auf die privaten mapStateToProps. Meinst du was anderes oder kannst du das konkretisieren?

@mordrax

Sie können überprüfen, ob alle Eigenschaftsnamen korrekt sind

Wenn Sie also Ihr mapStateToProps verfügbar machen und es öffentlich machen, sagen Sie, dass Sie Ihre ContainerComponent aus Ihrem Test rendern, einschließlich des Einsendens eines gefälschten Speichers und des Einsendens einer Zustandsstütze, dann empfängt Ihr mapStateToProps diesen Zustand und ordnet ihn einer Stütze zu. Aber wie können Sie es dann von diesem Punkt aus testen? Connect ruft mapStateToProps auf und führt so die Requisiten zusammen, aber wo ist die Naht und welcher Punkt ist die Naht im Code während dieses Prozesses/Flows, wo Sie die Requisiten abfangen können, die auf der Präsentationskomponente festgelegt werden? Ich denke, doppeltes Flachwasser wie @guatedude2 könnte eine Möglichkeit sein, die abgebildeten Requisiten ohne Nähte zu überprüfen ...

auch auf eurer seichten.flachen. Also gibt die connect()-Komponente was zurück, einen Wrapper, richtig? Und dieser Wrapper, der die Präsentationskomponente umschließt?

@granmoe kannst du das genauer erklären, was du genau meinst:

Wenn Sie auch Abdeckung für die Funktionen haben, die Sie an Connect übergeben, sollten Sie eine vollständige Abdeckung für Ihre Komponente als Ganzes haben.

Ich bin auch mit @tugorez in Bezug auf _was_ ich testen möchte und warum.

@markerikson so schönes Beispiel mit dem Spion. Es ist eine Sache, die Sie testen können, aber Sie würden dort mehr Prüfer benötigen, um die "Einheit des Verhaltens" zu testen. Nur zu testen, ob eine Spionageaktion von mapDispatchToProps aufgerufen wurde, sagt uns nicht wirklich die ganze Geschichte über die Containerkomponente. Es bestätigt nicht das erwartete Ergebnis basierend auf der Handler-Logik Ihres Containers.

Es reicht nicht aus, nur zu testen, ob Sie Props oder State bestanden haben, Sie möchten auch das Verhalten der Containerkomponente testen. Das bedeutet, zwei Dinge zu testen:

1) Hat die Containerkomponente die Requisiten mit der Präsentationskomponente verbunden, und wenn ja, gibt es einen bestimmten Zustand, den ich von der Präsentationskomponente erwarte, sobald sie basierend auf diesem bestimmten Zustand gerendert wird, der von Requisiten an die Präsentationskomponente übergeben wird? Wer weiß, vielleicht kommt ein dummer Entwickler daher und fügt mapStateToProps mehr Code hinzu. Sie können nicht immer darauf vertrauen, dass es die Dinge richtig abbildet, das ist der Sinn des Testens der Containerlogik. Obwohl mapStateToProps nicht wirklich logisch ist, wer weiß, dass wieder ein Entwickler vorbeikommt und eine if-Anweisung einfügt ... nun, das ist ein Verhalten, das die Dinge durcheinander bringen könnte.

2) Funktioniert die Dispatch-Handler-Logik (Verhalten) im Container.

@dschinkel :

Die typische Vorgehensweise zum Definieren von mit Redux verbundenen Komponenten ist export default connect()(MyComponent) und auch export MyComponent als benannter Export. Da mapState nur eine Funktion ist, können Sie in ähnlicher Weise auch export const mapState = () => {} importieren und dann separat testen.

Was das Testen der „Container-Komponente“ im Vergleich zur „Präsentationskomponente“ betrifft: Der allgemeine Gedanke hier ist, dass Sie sich wahrscheinlich größtenteils nicht um das Testen des Containers kümmern müssen, wobei „Container“ === „die Ausgabe von connect ". React-Redux hat bereits eine vollständige Testsuite dafür, wie sich das verhalten sollte, und es gibt keinen Grund für Sie, diesen Aufwand zu verdoppeln. Wir _wissen_, dass es das Abonnieren des Stores, das Aufrufen mapState und mapDispatch und das Übergeben von Requisiten an die verpackte Komponente korrekt handhabt.

Was _Sie_ als Benutzer der Bibliothek interessieren wird, ist, wie sich _Ihre_ Komponente verhält. Es sollte eigentlich keine Rolle spielen, ob Ihre eigene Komponente Requisiten von einem connect -Wrapper, einem Test oder etwas anderem erhält - es geht nur darum, wie sich diese Komponente angesichts eines bestimmten Satzes von Requisiten verhält.

(Außerdem, FWIW, ist mir klar, dass Sie absolut der Experte für Tests sind und ich nicht, aber es scheint, als würden Sie ein bisschen paranoid in Bezug auf die Dinge :) Wenn Sie sich Sorgen um ein mapState -Wesen machen versehentlich kaputt, dann schreiben Sie Tests dafür und fahren Sie fort.)

Wenn Ihre umschlossene Komponente dann andere verbundene Komponenten darin rendert, und insbesondere wenn Sie ein vollständiges Rendering anstelle eines flachen Renderings durchführen, ist es möglicherweise einfacher, die Dinge zu testen, indem Sie const wrapper = mount(<Provider store={testStore}><MyConnectedComponent /></Provider> . Aber insgesamt bin ich der Meinung, dass Sie die meiste Zeit in der Lage sein sollten, Ihre mapState -Funktion und die "einfache" Komponente separat zu testen, und Sie sollten wirklich nicht versuchen, die verbundene Version zu testen, nur um dies zu überprüfen dass die Ausgabe von mapState an die Plain-Komponente übergeben wird.

Als Referenz können Sie sich die Miniaturversion von connect ansehen, die Dan vor einiger Zeit geschrieben hat, um zu veranschaulichen, was es intern tut: https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e .

Ja, ich meine, wenn Sie MapStateToProps und DispatchStateToProps exportieren, wäre das besser. Ich möchte nicht testen, ob connect() (der Mitarbeiter in meinem Test) funktioniert, definitiv nicht. Das wäre also eine klare Möglichkeit, diese beiden Methoden zu exportieren:

example-container-spec.js

describe('testing mapPropsToState directly', () => {
    it.only('returns expected state', () => {
        const state = {firstName: 'Dave'};
        const output = mapStateToProps(state);

        expect(output.firstName).to.equal('Dave');
    });
});

Container

import React from 'react';
import { connect } from 'react-redux'
import Example from './Example';

export const mapStateToProps = (state) => ({
    firstName: state.firstName
})

export default connect(mapStateToProps)(Example);

Präsentation

import React, { Component } from 'react';

export default class Example extends Component {
    render(){
        return (
            <div className='example'>
                {this.props.firstName}
            </div>
        )
    }
}

Andererseits könnten Sie auch so etwas mit _flachem_ Rendering machen und trotzdem in die untergeordnete Komponente eintauchen:

example-container-spec.js

it('persists firstName to presentation component', () => {
        var state = {firstName: "Dave"};
        const container = mount(<ExampleContainer store={fakeStore(state)}/>),
            example = container.find(Example);

        expect(example.length).to.equal(1);
        expect(example.text()).to.equal(state.firstName);
    });

Container

import React from 'react';
import { connect } from 'react-redux'
import Example from './Example';

const mapStateToProps = (state) => ({
    name: state.firstName
})

export default connect(mapStateToProps)(Example);

Präsentation

import React, { Component } from 'react';

export default class Example extends Component {
    render(){
        return (
            <div className='example'>
                {this.props.firstName}
            </div>
        )
    }
}

Eine weitere Möglichkeit, dies oberflächlich zu tun, besteht darin, eine doppelte seichte Übung zu machen. Shallow on the shallow parent wird die untergeordnete Komponente, die sie umschließt, flach machen, etwa so:

it('get child component by double shallow()', () => {
        const container = shallow(<ExampleContainer store={fakeStore({})}/>),
            presentationalChildComponent = container.find(Example).shallow().find('.example');

        expect(presentationalChildComponent).to.have.length(1);
    });

Es ist also nur eine andere Möglichkeit, das Ergebnis Ihrer Präsentationskomponente zu überprüfen.

So oder so würde funktionieren ... aber wie Sie sagten, ist das Exportieren von mapStateToProps wahrscheinlich am besten, da es mir nur darum geht, den Prop-Status zu testen.

Mir ist klar, dass Sie absolut der Experte für Tests sind und ich nicht, aber es scheint, als würden Sie ein bisschen paranoid in Bezug auf die Dinge :)

Nein, das bin ich nicht, richtiges Testen bedeutet, dass es wichtig ist, gute Tests zu haben, die nicht spröde sind und einen Mehrwert bieten . Zunächst einmal ist niemand ein _Experte_. Jeder lernt ständig dazu, manche wollen es aus Egoismus nicht zugeben. Es heißt Software Craftsmanship (auch bekannt als Professionalität).

Es ist wichtig, nur das Verhalten zu testen und nicht die Mitarbeiter (connect() selbst) zu testen, wie Sie gesagt haben. Das zum ersten Mal herauszufinden und gut zu testen, ist wichtig. Es ist wichtig, sicherzustellen, dass Sie keine Mitarbeiter testen, und das erfordert manchmal Diskussionen unter anderen Entwicklern.

Sie können spröde Tests haben, und es ist wichtig, keine spröden Tests zu haben. Das Testen sollte ernst genommen werden, und das bedeutet, dass Sie ständig überprüfen müssen, ob Sie es richtig angehen. Ich versuche einfach, meinen Flow herauszufinden. I TDD, also wie ich beginne, ist wichtig, und gute Tests sind dabei wichtig. Daher sollte niemand das Gefühl haben, "paranoid" zu sein, wenn er versucht, verschiedene Möglichkeiten zum Testen von React-Komponenten zu verstehen.

Neues React Test Repo in Kürze ...
Tatsächlich werde ich bald ein Repo teilen, das verschiedene Stile und Kombinationen und Ansätze zum Testen von React-Redux zeigt. Ich denke, es wird den Leuten helfen, weil ich nicht der Einzige bin, der darüber nachdenkt. Immer wieder sieht man Leute, die die gleichen Fragen zum Testen von Redux-Containern und anderen Komponenten stellen. Die Redux-Dokumentation und die React-Redux-Dokumentation werden dem nicht gerecht, ihnen fehlt meiner Meinung nach die Dokumentation im Testbereich. Es kratzt nur an der Oberfläche, und wir brauchen ein nettes Repo, das verschiedene Stile für die Annäherung an React-Tests zeigt, also werde ich das bald veröffentlichen.

If anyone would like to contribute to that repo, please get a hold of me damit ich dich als Mitbearbeiter hinzufügen kann. Ich würde gerne Leute Beispiele hinzufügen lassen. Ich würde gerne Beispiele mit direkten React Test Utils + mocha sehen, mit Enzyme , Ava , Jest , Tape usw.

Fazit :

Ich denke, beide Ansätze, die wir besprochen haben, sind in Ordnung. Testen Sie die Methode direkt oder testen Sie sie, wie ich es oben getan habe, und tauchen Sie ein. Manchmal möchten Sie Ihre Tests nicht auf eine bestimmte Methode stützen, weil Sie zerbrechliche Tests erhalten können ... daher wurden die Leute in der Vergangenheit mit Tests gestochen. Ob also eine Funktion getestet werden soll oder nicht, hängt davon ab, ob das die "Einheit" testet. Manchmal möchten Sie eine Methode nicht öffentlich machen, und es ist möglicherweise besser, den Vertrag darüber zu testen und einen Teil davon privat zu halten. Es ist also immer wichtig, beim Schreiben von Tests oft darüber nachzudenken, was man tut. Daran ist nichts auszusetzen. Gute Tests zu schreiben ist nicht einfach.

Genau das fördert TDD, das oft zu tun. Damit Sie am Ende einen schlankeren, besseren, weniger zerbrechlichen Code erhalten. TDD zwingt Sie dazu, über das Testen im Voraus nachzudenken, nicht später. Das ist der Unterschied und warum Leute wie ich verschiedene Abläufe und Ansätze verstehen wollen, weil ich es tun muss, wenn ich TDD mache, zwingt es mich, das Design oft in kleinen Stücken neu zu bewerten, und das bedeutet, oft über meinen Testansatz nachzudenken.

@markerikson danke für deine Beiträge Ich liebe es, über Tests zu sprechen. Guter Stoff, Mann. Ich habe Ihr Fachwissen nicht in Frage gestellt, nur dass ich selbst nicht versucht hatte, mapStateToProps direkt zu testen! Als Nicht-Tester machst du es nicht schlecht ;). Ich bin einfach neu in Redux, einfach so, also werde ich Fragen haben, die nicht nur "die Oberfläche berühren". Wir sollten das Testen auf einer niedrigeren Ebene diskutieren, damit die Leute verstehen, was sie tun, wie sie es tun können und ob sie von ihrem Ansatz profitieren. Dazu müssen Sie Connect, React-Redux, Provider auf einer niedrigeren Ebene verstehen ... damit Sie wissen, wie man "nur das Verhalten testet". Ich habe nicht viele Leute gesehen, die mapStateToProps exportiert haben, was auch für mich interessant ist ... und ich frage mich, warum nicht.

WeDoTDD.com
Übrigens für alle Interessierten, ich habe letztes Jahr WeDoTDD.com gestartet (übrigens in React geschrieben). Wenn ein bestimmtes Team (alle Entwickler in einem bestimmten Team) TDDs oder Ihr gesamtes Unternehmen (einige Unternehmen haben Entwickler, in denen alle TDDs sind, insbesondere Beratungsunternehmen), kontaktieren Sie mich bitte unter slack.wedotdd.com

Aktualisieren.

Nachdem ich mehr mit dem Testen verbundener Komponenten gespielt und darüber nachgedacht habe, was für mich großartig funktioniert, stimme ich jetzt zu 100 % der Aussage von @markerikson hier zu:

Wenn Sie tatsächlich das Gefühl haben, eine angeschlossene Komponente testen zu müssen, sollten Sie in der Lage sein, zu rendern ohne sich um Anbieter oder Kontext oder irgendetwas kümmern zu müssen.

Ich habe keine Notwendigkeit gesehen, <Provider /> in meine Tests einzuführen, und es scheint, als würden Sie einen Mitarbeiter testen, wenn Sie das tun, wenn es nicht darum geht, zu testen, ob <Provider /> funktioniert. Sie sollten testen, ob das Verhalten in Ihrer Komponente funktioniert, und es spielt keine Rolle, ob Ihr Container den Speicher über den Kontext erhält. Passieren Sie den Store einfach als Requisite. Connect muss nur einen Weg zum Store erreichen, es ist egal, wie (Kontext oder Requisiten) ... Provider in Ihren Tests vollständig loswerden.

Ich sehe auch keine Notwendigkeit, die Kontextoption von Enzym zu verwenden, was eine weitere Möglichkeit ist, den Speicher über den Kontext von React zu speichern. Vergessen Sie den Kontext von React in Ihren Tests völlig, es ist völlig unnötiges Aufblähen und macht Ihre Tests komplexer und viel schwieriger zu warten und zu lesen.

Testen Sie Ihre reine mapStateToProps-Funktion direkt, indem Sie sie in Ihre Containerdatei exportieren. Ich verwende eine Kombination aus Testen von mapStateToProps + auch ein paar Tests, die meinen Container oberflächlich testen, um sicherzustellen, dass die Präsentationskomponente zumindest die Requisiten erhält, die ich erwarte, und dann höre ich dort mit dem Requisitentest auf. Ich habe mich auch entschieden, für meine Containerkomponententests kein doppeltes Flachwasser durchzuführen. TDD gibt mir Feedback, dass die Tests zu kompliziert werden und Sie wahrscheinlich etwas falsch machen, wenn Sie dies versuchen. Das "falsch" ist "kein oberflächliches Zeug für Ihre Containerkomponententests verdoppeln". Erstellen Sie stattdessen eine neue Suite von Präsentationskomponententests, die die Ausgabe Ihrer Präsentationskomponenten unabhängig testen.

Freut mich zu hören, dass Sie die Dinge geklärt haben. (FWIW, meine Kommentare waren größtenteils eine Antwort auf Ihre Fragen zu "Nähten" und "Eingraben in Requisiten", die wirklich so aussahen, als würden sie mit wenig oder gar keinem Nutzen zu unnötigen Details gehen.)

Die Verwendung eines <Provider> /context _is_ wird notwendig sein, wenn Sie eine Komponente vollständig rendern, die andere verbundene Komponenten rendert, da diese verbundenen Kinder im Kontext nach dem Geschäft suchen.

Wenn Sie Vorschläge zur Verbesserung der aktuellen Dokumentation von Redux zum Testen haben, reichen Sie auf jeden Fall eine PR ein.

@markerikson guter Punkt auf dem Berg. Ich würde jedoch nur einen Mount machen, wenn ich den Lebenszyklus der Komponente testen müsste, die ich teste. Ich würde es nicht verwenden, um tief in untergeordnete Komponenten einzutauchen ... das würde sich nicht mehr als Komponententest qualifizieren und ist im Grunde ein Integrationstest, und Sie würden diesen Test mit Implementierungsdetails koppeln, wenn Sie diese untergeordneten Komponenten auf ihren testen sollten isoliert besitzen, nicht durch eine übergeordnete Komponente.

Trotzdem gute Sache, danke!

Richtig, ich sage nur, wenn Sie mount verwenden, um den Lebenszyklus der fraglichen Komponente zu überprüfen, und diese Komponente andere verbundene Komponenten rendert, dann _müssen_ Sie den Speicher im Kontext für diese verschachtelten Komponenten verfügbar haben, um Fehler zu vermeiden Die zu testende Komponente wird gerendert.

(Bearbeiten: Ich wiederhole mich anscheinend irgendwie, sorry - die Gefahren, Fragen an mehreren Stellen zu beantworten, ohne immer einen Thread zurückzugehen oder sogar nur ein bisschen nach oben zu scrollen.)

ah ok verstanden! ja daran habe ich nicht gedacht...

Danke für all die Kommentare hier, @markerikson und @dschinkel! Sie sind sehr hilfreich. Ich habe mich jetzt entschieden, meine nicht verbundene Komponente zu exportieren und sie wie jede andere zu testen, und auch meine mapStateToProps und mapDispatchToProps separat zu testen. Es gibt jedoch einen Fall, den diese Tests nicht abfangen, und das ist, wenn map(State/Dispatch)ToProps nicht wirklich an den Aufruf von connect übergeben wird. Beispiel:

import React from 'react';
import { connect } from 'react-redux';

export const mapStateToProps = ({ name }) => ({ name });

export const Example = ({ name }) => <h1>{name}</h1>;

export default connect({ name } => ({ name: firstName }))(Example); // Whoops, didn't actually pass in `mapStateToProps`.

Dies wird in der Praxis wahrscheinlich nie passieren, aber es könnte sein, zumal Linters mapStateToProps nicht als unbenutzte Variable melden würden, da es exportiert wird.

@danny-andrews Könnten Sie genauer sagen, wie Sie Ihre mapDispatchToProps-Funktion testen können?

@Leizard

Ich teste mapDispatchToProps oder mapStateToProps nicht mehr direkt. Ich habe meine Meinung seit diesem Thread geändert und rate davon ab.

Wenn Sie sie auch testen, werden Sie auf das Problem von @danny-andrews stoßen, aber es geht mehr um gute Tests, und der Versuch, diese beiden Funktionen verfügbar zu machen, ist keine gute Idee. Das Testen ist vorbei. Sie sollten diese beiden Methoden indirekt testen, indem Sie das Verhalten testen oder indem Sie stattdessen die Requisiten durch Ihren flachen Container testen. Ich fand, dass es keinen Grund gibt, zu versuchen, diese privaten Methoden offenzulegen, und mir wurde jetzt klar, dass es auch eine schlechte Testpraxis war, dies zu versuchen.

Also bin ich mit @markerikson nicht einverstanden, teste durch deine verbundene Komponente.

Die richtige API/den richtigen Vertrag zum Testen zu finden und nicht zu viel zu testen, das ist der Schlüssel, Baby :).

@dschinkel

Sie sollten diese beiden Methoden indirekt testen, indem Sie das Verhalten testen ...
Also bin ich mit @markerikson nicht einverstanden, teste durch deine verbundene Komponente.

Schlagen Sie vor, dass Sie auf den Export der nicht verbundenen Komponente vollständig verzichten und nur die verbundene Komponente testen sollten? Ich denke , das ist "Übertestung". Dadurch werden Ihre Tests unnötigerweise mit den Implementierungsdetails der getesteten Komponente verknüpft. Weil die Funktionalität der nicht verbundenen Komponente (falls vorhanden, ist das eine ganz andere Wurmkiste) völlig unabhängig davon ist, woher sie ihre Requisiten erhält. Vielleicht möchten Sie diese Komponente in Zukunft zu einer reinen Präsentationskomponente machen. Wenn Sie die verbundene Komponente testen, müssen Sie alle Ihre Tests ändern, um keinen Store mehr einzuspeisen, sondern die Requisiten direkt zu übergeben. Wenn Sie die nicht verbundene Komponente getestet haben, müssen Sie nichts weiter tun, als die für verbundene Komponenten spezifischen Tests wegzublasen (mehr dazu weiter unten).

Ich stimme jedoch zu, dass Sie mapStateToProps / mapDispatchToProps nicht direkt testen sollten. Diese privaten Methoden nur zu Testzwecken offenzulegen, fühlte sich für mich immer wie ein Code-Geruch an. Tatsächlich denke ich, dass dies das einzige Mal ist, dass Sie die angeschlossene Komponente testen sollten. Also zusammenfassend:

  1. Nicht verbundene Komponente und verbundene Komponente exportieren
  2. Exportieren Sie NICHT mapStateToProps oder mapDispatchToProps
  3. Testen Sie die gesamte Komponentenlogik über die nicht verbundene Komponente
  4. Testen Sie nur verbundene Komponenten, wenn Sie die Interaktion mit Redux testen (Datenstützen werden von der richtigen Stelle im Geschäft übergeben/Aktionsstützen werden den richtigen Aktionserstellern zugewiesen usw.).

Der einzige zusätzliche Overhead bei Nummer 4 besteht darin, dass Sie den Redux-Speicher abzweigen müssen, um ihn an Ihre angeschlossene Komponente weiterzuleiten. Dies ist jedoch ziemlich einfach, da die API nur aus drei Methoden besteht.

MapStateToProps-Methode

TestContainer.jsx:

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

export const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer); // Ewww, exposing private methods just for testing

TestContainer.spec.jsx:

import React from 'react';
import { shallow } from 'enzyme';

import TestContainer, { mapStateToProps } from './TestContainer';

it('maps auth.userPermissions to permissions', () => {
  const userPermissions = {};

  const { permissions: actual } = mapStateToProps({
    auth: { userPermissions },
  });

  expect(actual).toBe(userPermissions);
});

Neue Methode

TestContainer.jsx:

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer);

TestContainer.spec.jsx:

import React from 'react';
import { shallow } from 'enzyme';

import TestContainer, { TestComponent } from './TestContainer';

it('renders TestComponent with approriate props from store', () => {
  const userPermissions = {};
  // Stubbing out store. Would normally use a helper method. Did it inline for simplicity.
  const store = {
    getState: () => ({
      auth: { userPermissions },
    }),
    dispatch: () => {},
    subscribe: () => {},
  };
  const subject = shallow(<TestContainer store={store} />).find(TestComponent);

  const actual = subject.prop('permissions');
  expect(actual).toBe(userPermissions);
});

Ja, das sage ich, ich exportiere und teste mapStateToProps nicht , es ist zu viel getestet. Testen Sie über die API . Die API ist hier der Container .

@Danny Andrews

Testen Sie die gesamte Komponentenlogik über die nicht verbundene Komponente

kannst du ein paar beispiele nennen?

Ich meine dasselbe wie Sie, als Sie sagten: "Sie sollten diese beiden Methoden indirekt testen, indem Sie das Verhalten testen."

Es könnte hilfreich sein, die Begriffe zu klären: Wenn ich "nicht verbundene Komponente" sage, meine ich die Rohkomponente, die in den Aufruf von connect ist, und wenn ich "verbundene Komponente" sage, meine ich die resultierende Komponente, von der zurückgegeben wird der Aufruf von connect . "Angeschlossene Komponente" und "Container" sind meines Erachtens Synonyme. Von meiner oben:

// Unconnected component. Test all component logic by importing this guy.
export const TestComponent = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

// Connected component (container). Only test interaction with redux by importing this guy.
export default connect(mapStateToProps)(TestComponent);

Einige Leute würden es vorziehen, diese vollständig aufzuteilen und ihre "Container" nur Aufrufe von connect zu haben, die eine reine Präsentationskomponente umschließen. In diesem Fall verschwinden Nummer 1 und 3 auf der Liste und die einzigen Tests, die Sie schreiben müssen, sind diejenigen, die die Interaktion mit Redux (Nummer 4) überprüfen.

Ich habe vor, einen RIESIGEN Blogbeitrag darüber zu schreiben. Ich bin im Moment mit Code beschäftigt, werde aber später antworten

Nachdem ich diesen Thread mehrmals gelesen habe, bin ich zu dem Schluss gekommen, dass es 3 empfohlene Vorgehensweisen gibt:

  1. Exportieren Sie mapDispatchToProps und mapStateToProps und testen Sie sie separat
  2. Rendern Sie die Container-Komponente flach und testen Sie, ob die Verkabelung korrekt ist
  3. Verwenden Sie Selektoren anstelle von mapStateToProps und Aktionsersteller anstelle von mapDispatchToProps und testen Sie diese separat. Schreiben Sie Tests, indem Sie die Aktionsersteller, Reduzierer und Selektoren zusammen verwenden, um sicherzustellen, dass der gesamte Ablauf funktioniert.

Am Ende denke ich, dass alle Optionen gültig sind, aber ihre eigenen Vor- und Nachteile haben. Das "reinste" ist wohl das 2., macht aber auch die meiste Arbeit. Meine persönliche Präferenz würde eigentlich in Richtung Option 3 gehen.
Ich habe ausführlicher darüber in meinem Blog geschrieben, wo ich auch einige Codebeispiele habe.

@ucorina
In der aktuellen Welt der ES6-Module wird jede Datei als Modul betrachtet, das in seinen eigenen Geltungsbereich eingeschlossen ist. Exporting ist Ihre API des kleinen Moduls und es fühlt sich super falsch an, Methoden zu exportieren, nur damit Sie sie testen können. Es ist ähnlich, wie Sie private Methoden öffentlich machen würden, nur um sie zu testen.

Deshalb tendiere ich zum @dschinkel- Ansatz und exportiere mapDispatch und mapState nicht, da sie für mich eine private Implementierung der verbundenen Komponente sind.

Ich bin mir jedoch nicht sicher, was ich mit einer Komponente tun soll, die nur eine andere Komponente um eine Verbindung wickelt.

Hast du jemals den Blogbeitrag geschrieben? @dschinkel würde es gerne lesen

@AnaRobynn

Ja, ich stimme Ihnen zu, dass es sich falsch anfühlt, nur mapDispatch und mapState zu exportieren, und ich würde es wahrscheinlich nicht selbst verwenden, aber es ist eine gültige Option, auch wenn sie weniger "rein" ist . Ich habe es dort als mögliche Option gelassen, auch weil Dan Abramov genau diese Technik hier vorgeschlagen hat: https://github.com/reduxjs/react-redux/issues/325#issuecomment -199449298.

Um Ihre Frage zu beantworten, "was mit einer Komponente zu tun ist, die nur eine andere Komponente um eine Verbindung wickelt", würde ich sagen, testen Sie sie einfach nicht. Ihre Anwendung hat keine Geschäftslogik und Sie möchten die connect -Implementierung nicht testen – diese wurde bereits in der react-redux -Bibliothek getestet.

Wenn Sie trotzdem testen möchten, um Ihren Code zukunftssicher zu machen, können Sie immer dem 2. Ansatz folgen, den ich hier beschreibe, der näher an dem liegt, was @dschinkel beschreibt.

@ucorina Genau, ich denke, die Option ist die, für die ich mich auch entscheiden würde! Vielen Dank für die ausführlichere Erläuterung Ihrer Antwort. Es ist ein schön geschriebener Blogbeitrag. Herzlichen Glückwunsch!

@ucorina Entschuldigung für die erneute Störung, aber nachdem ich eine Weile darüber geschlafen habe, bin ich mir auch nicht sicher, ob ich Option 2 vollständig zustimme. Ich bin mir aber nicht sicher. Ich bin mir auch nicht sicher, welchen Nutzen das Testen von mapStateToProps überhaupt hat.

Finden Sie es in Ordnung, dass „Option 2“ indirekt auch die Aktionsersteller testet? Und auch indirekt Selektoren testen? Ist das eine gute Sache?
Ich bin davon nicht zu 100% überzeugt, aber ich bin auch verwirrt über all die (Fehl-)Informationen da draußen. Es gibt so viele verschiedene Ideen zum Testen.

  1. Verwenden Sie nur Selektoren innerhalb mapStateToProps

    • Verspotten Sie sie bei Bedarf während Containertests

    • Selektoren separat testen.

    • Verwenden Sie Aktionsersteller nur innerhalb mapDispatchToProps

    • Verspotten Sie sie bei Bedarf während Containertests.

    • Aktionsersteller separat testen.

    • Flache die angeschlossene Komponente

    • behaupten, dass es gerendert wird

    • Testen Sie jede zusätzliche Logik, die zu mapStateToProps , mapDispatchToProps oder mergeProps hinzugefügt werden musste und nicht bereits an anderer Stelle getestet wurde. (bedingte Versendungen, Selektoren, die Argumente von ownProps nehmen, etc.)



      • In diesem Fall testen Sie nur, dass die Mocks die richtige Anzahl von Malen und mit den richtigen Argumenten aufgerufen wurden.



Alternative:

  • Scheinspeicherstatus zum gemeinsamen Testen von Container und Selektoren
  • Verspotten Sie außerhalb von API-Aufrufen, um Container und Aktionen zusammen zu testen

@AnaRobynn Ich glaube, der zweite Ansatz von @ucorina ist der richtige.

Vieles, was ich höre, deutet darauf hin:

  1. Sie sollten mapDispatchToProps verfügbar machen und direkt testen
  2. Sie müssen Ihren Aufruf von connect() nicht testen, da connect() bereits getestet ist

In Bezug auf 1 halte ich es nicht für eine gute Praxis, Interna zum Testen offenzulegen. Ihre Tests A: nehmen jetzt eine interne Struktur an und B: sollten das Ding testen, wie sie tatsächlich verwendet werden.

In Bezug auf 2 sollten Sie _unbedingt_ etwas testen, das eine externe Bibliothek aufruft. Dies ist einer der Schwachpunkte in jeder Anwendung zum Brechen. Bei jeder Bibliothek ist es immer noch möglich, dass eine Breaking Change in einem Minor-/Patch-Versionsstoß eingeführt wird, und Sie _wollen_, dass Ihre Tests schnell fehlschlagen.

Finden Sie es in Ordnung, dass „Option 2“ indirekt auch die Aktionsersteller testet? Und auch indirekt Selektoren testen? Ist das eine gute Sache?

Ja, redundante Testabdeckung ist eine gute Sache.

@philihp @dougbacelar
Ich glaube, ich bin ein bisschen mit dem Testen von React-Anwendungen verbunden. Meine aktuelle Meinung und Meinung zum Thema:

  1. Stellen Sie mapStateToProps und mapDispatchToProps nicht bereit, wenn Sie diese Zuordnungen nicht in mehreren Komponenten benötigen. Sie nur zum Testen zu exportieren, ist ein Anti-Pattern.
  2. Ich sehe derzeit Container als Kollaborateure (Funktionen, die andere Funktionen delegieren, und diese anderen Funktionen führen die eigentliche Logik aus).
    Was bedeutet das?
  3. Container führen keine Logik aus
  4. Die Logik zum Auswählen eines Zustands ist für Selektorfunktionen (die rein einheitengetestet werden können).
  5. Die Logik für Aktionen ist in Aktionserstellern verborgen (was davon abhängen kann, ob Sie Redux-Thunk verwenden oder nicht selbst Mitarbeiter sind).
  6. Die gerenderte Ansicht ist eine weitere Funktion (die möglicherweise eine andere Kollaborationsfunktion hat oder nur Dinge rendert

=> Das Test-Setup dafür ist eigentlich ziemlich einfach, wenn Sie die richtigen Testbibliotheken verwenden (ich empfehle testdouble.js).
=> Verspotten Sie Ihren Selektor und Ihre Aktionen über testdouble.js (mit td.when() )
=> Rendern Sie Ihre Container-Komponente flach, dive() einmal, um Zugriff auf die Methoden der View-Komponente zu erhalten
=> Gegeben das Regelwerk von td.when() bestätigen, ob sich die Komponente korrekt verhält

Sie müssen keine fakeStore-Bibliothek injizieren, es ist in Ordnung, den Laden zu löschen.

Beispiel:

/** <strong i="27">@format</strong> */

import React from 'react';
import { shallow } from 'enzyme';

function setup(props) {
  const HasOrganization = require('./HasOrganization').default;
  const defaultProps = {
    store: {
      subscribe: Function.prototype,
      getState: Function.prototype,
      dispatch: Function.prototype,
    },
  };
  const container = shallow(<HasOrganization {...defaultProps} {...props} />);
  return {
    container,
    wrapper: container.dive(),
  };
}

describe('<HasOrganization /> collaborations', () => {
  let getCurrentOrganizationId;
  beforeEach(() => {
    getCurrentOrganizationId = td.replace('../containers/App/userSelectors')
      .selectCurrentOrganizationId;
  });

  test('not render anything when the id is not valid', () => {
    const Dummy = <div />;
    td.when(getCurrentOrganizationId()).thenReturn(null);
    const { wrapper } = setup({ children: Dummy });

    expect(wrapper.find('div').length).toBe(0);
  });

  test('render something when the id is valid', () => {
    const Dummy = <div />;
    td.when(getCurrentOrganizationId()).thenReturn(1);
    const { wrapper } = setup({ children: Dummy });

    expect(wrapper.find('div').length).toBe(1);
  });
});

Gedanken?

@AnaRobynn Stimme den Punkten 1 und 2 voll und ganz zu!

Meine Container sind normalerweise sehr geradlinig:

// imports hidden

const mapStateToProps = (state, { userId }) => ({
  isLoading: isLoading(state),
  hasError: hasError(state),
  placeholders: getPlaceholders(userId)(state), // a list of some sort
  shouldDoStuff: shouldDoStuff(state),
});

const mapDispatchToProps = {
  asyncActionPlaceholder,
};

export const mergeProps = (stateProps, dispatchProps, ownProps) => {
  return {
    ...stateProps,
    ...dispatchProps,
    ...ownProps,
    onMount: () => {
      if (stateProps.shouldDoStuff) {
        dispatchProps.asyncActionPlaceholder(ownProps.userId);
      }
    },
  };
};

export default compose(
  connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps
  ),
  callOnMount(props => props.onMount())
)(SampleComponent);

So würde ich

  1. Spionieren Sie alle Selektoren und Aktionsersteller aus
  2. test getPlaceholders Spy wurde mit der richtigen UserId aufgerufen
  3. behaupten, dass SampleComponent mit den richtigen Requisiten gerendert wurde
  4. Rufen Sie die Prop onMount von SampleComponent und vergewissern Sie sich, dass sie die verspottete Aktion auslöst, wenn shouldDoStuff===true
  • Dinge wie das bedingte Rendern von Dingen, die von Requisiten abhängen, würde ich in einem separaten Test für SampleComponent testen
  • Ich ziehe es auch vor, redux-mock-store zu verwenden, um den Laden wie configureMockStore([thunk])() $ zu verspotten, aber ich denke, es ist in Ordnung, dies nicht zu tun ...

TL; DR Ich habe im Grunde nur Testselektoren mit korrekten Argumenten aufgerufen, verspottete Aktionen wurden erfolgreich abgesetzt (wenn sie von irgendeiner Logik abhängen) und dass die untergeordnete Komponente mit den richtigen Requisiten gerendert wird

@philihp

  • Ich bin sehr dagegen, externe Bibliotheken zu testen, ich ziehe es vor, alles zu verspotten und Dinge isoliert zu testen.
  • Ich vertraue darauf, dass End-to-End-Tests alle größeren Probleme erkennen, die durch Bibliotheks-Upgrades verursacht werden.
  • Der Grund, warum ich Container + Selektoren + Aktionsersteller nicht alle zusammen testen möchte, ist, dass Sie dieselben Selektoren und Aktionsersteller für jeden anderen Container, der sie wiederverwendet, erneut testen müssen. An diesem Punkt ist es besser, den Komponententests selbst weitere Testfälle hinzuzufügen.

@dougbacelar Können Sie einen Beispieltestcode geben, um die von Ihnen gezeigte Komponente zu testen. Sie verwenden Enzym? Etwas anderes? Seicht? Montieren?

@dougbacelar

Ich bin sehr dagegen, externe Bibliotheken zu testen, ich ziehe es vor, alles zu verspotten und Dinge isoliert zu testen.

Verspotten Sie, wenn Sie müssen, aber wenn Sie das Original verwenden können, warum nicht? Mocking ist nützlich, wenn bestimmte Aufrufe langsam sind oder Nebeneffekte wie Netzwerkanfragen haben.

Der Grund, warum ich Container + Selektoren + Aktionsersteller nicht alle zusammen testen möchte, ist, dass Sie dieselben Selektoren und Aktionsersteller für jeden anderen Container, der sie wiederverwendet, erneut testen müssen. An diesem Punkt ist es besser, den Komponententests selbst weitere Testfälle hinzuzufügen.

Ja , testen Sie Ihre Selektoren und Aktionsersteller unabhängig voneinander. Testen Sie sie gründlich mit vielen erwarteten Eingaben.

Ja , testen Sie auch Ihre Container. Wenn das bedeutet, dass sie sich überschneidende Berichterstattung über Selektoren und Aktionsersteller bieten, ist das keine schlechte Sache.

Hier ist, was ich meine ...

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { saveColor } from '../actions/save';
import { selectColors } from '../reducers/colors.js';
import ColorButtons from '../components/ColorButtons';
const ColorButtons = ({ colors, onClick }) => (
  <div>
    {colors.map(color => {
      <button type="button" key={color} onClick={onClick(color)}>{color}</button>
    })}
  </div>
);
ColorButtons.propTypes = {
  colors: PropTypes.arrayOf(PropTypes.string),
  onClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
  colors: selectColors(state.colors),
});
const mapDispatchToProps = dispatch => ({
  onClickColor: color => () => {
    dispatch(saveColor({ color }));
  },
});
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ColorButtons);
import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import ColorButtons from '../ColorButtons';
import { saveColor } from '../../actions/save';
import { selectColors } from '../../reducers/colors.js';
const buildStore = configureStore();
describe('<ColorButtons />', () => {
  let store;
  let wrapper;
  const initialState = { colors: ['red', 'blue'] };
  beforeEach(() => {
    store = buildStore(initialState);
    wrapper = shallow();
  });
  it('passes colors from state', () => {
    expect(wrapper.props().colors).toEqual(selectColors(initialState.colors));
  });
  it('can click yellow', () => {
    const color = 'yellow';
    wrapper.props().onClick(color)();
    expect(store.getActions()).toContainEqual(saveColor({ color }));
  });
});

Hier werden nicht alle verschiedenen Parameter getestet, die selectColors und saveColor bekommen könnten; Es wird getestet, ob <ColorButtons /> beim Erstellen die Eigenschaften erhält, die wir erwarten. Testen Sie diese unbedingt in einem anderen Test.

import { selectColors } from '../colors.js';
describe('selectColors', () => {
  it('returns the colors', state => {
    expect(selectColors(['red'])).toEqual(['red'])
  })
  it('returns an empty array if null', state => {
    expect(selectColors(null)).toEqual([])
  })
  it('returns a flattened array', state => {
    expect(selectColors(['red', ['blue', 'cyan']])).toEqual(['red','blue','cyan'])
  })
})

Ich bin sehr dagegen, externe Bibliotheken zu testen, ich ziehe es vor, alles zu verspotten und Dinge isoliert zu testen.

Verspotten Sie, wenn Sie müssen, aber wenn Sie das Original verwenden können, warum nicht? Mocking ist nützlich, wenn bestimmte Aufrufe langsam sind oder Nebeneffekte wie Netzwerkanfragen haben.

Stimme teilweise nicht zu.
Ich verspotte niemals externe Bibliotheken (verspotten Sie nicht, was Sie nicht besitzen), aber ich schreibe einen Wrapper um zum Beispiel Axios. In meinem Kollaborationstest kann ich einfach alle Funktionen nachspielen und sicherstellen, dass alles richtig verkabelt ist.

In meinen Collaboration-Tests mockiere ich immer Module, Funktionen etc
Diese von Ihrem Mitarbeiter aufgerufenen Funktionen können einfach getestet werden.

Hier ist, was ich meine ...

Auch nicht einverstanden.
Sie testen hier implizit Ihre Reduzierer und Selektoren. Wenn Sie das gut machen wollen, aber ich ziehe es vor, diese Funktionen zu verspotten und einfach zu überprüfen, ob sie richtig aufgerufen werden. Den Rest erledigen die Reduzierer/Selektoren.

Dieser Thread wiederholt Punkte aus https://martinfowler.com/articles/mocksArentStubs.html , Classical and Mockist Testing

Einverstanden. Verriegelung.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen