webentwicklung-frage-antwort-db.com.de

Testen von ausgelösten Aktionen in Redux mit Jest

Ich bin ziemlich neu bei Jest und bin zwar kein Experte beim Testen von asynchronem Code ... 

Ich habe einen einfachen Fetch-Helfer, den ich verwende:

export function fetchHelper(url, opts) {
    return fetch(url, options)
        .then((response) => {
            if (response.ok) {
                return Promise.resolve(response);
            }

            const error = new Error(response.statusText || response.status);
            error.response = response;

            return Promise.reject(error);
        });
    }

Und implementiere es gerne so:

export function getSomeData() {
    return (dispatch) => {
        return fetchHelper('http://datasource.com/').then((res) => {
            dispatch(setLoading(true));
            return res.json();
        }).then((data) => {
            dispatch(setData(data));
            dispatch(setLoading(false));
        }).catch(() => {
            dispatch(setFail());
            dispatch(setLoading(false));
        });
    };
}

Ich möchte jedoch testen, dass die korrekten Sendungen unter den richtigen Umständen und in der richtigen Reihenfolge abgefeuert werden.

Mit einer sinon.spy() war das früher ziemlich einfach, aber ich kann nicht recht herausfinden, wie ich dies in Jest replizieren kann. Im Idealfall würde mein Test so aussehen:

expect(spy.args[0][0]).toBe({
  type: SET_LOADING_STATE,
  value: true,
});


expect(spy.args[1][0]).toBe({
  type: SET_DATA,
  value: {...},
});

Vielen Dank im Voraus für jegliche Hilfe oder Ratschläge!

7
DanV

Die redux docs haben einen großartigen Artikel zum Testen von Erstellern von asynchronen Aktionen :

Für asynchrone Aktionsersteller, die Redux Thunk oder andere Middleware verwenden, ist es am besten, den Redux-Store für Tests vollständig zu verspotten. Sie können die Middleware mit redux-mock-store auf einen Mock-Store anwenden. Sie können auch fetch-mock verwenden, um die HTTP-Anforderungen zu simulieren.

import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
import fetchMock from 'fetch-mock'
import expect from 'expect' // You can use any testing library

const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)

describe('async actions', () => {
  afterEach(() => {
    fetchMock.reset()
    fetchMock.restore()
  })

  it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => {
    fetchMock
      .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } })


    const expectedActions = [
      { type: types.FETCH_TODOS_REQUEST },
      { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
    ]
    const store = mockStore({ todos: [] })

    return store.dispatch(actions.fetchTodos()).then(() => {
      // return of async actions
      expect(store.getActions()).toEqual(expectedActions)
    })
  })
})

Ihr Ansatz besteht darin, nicht mit Scherz (oder Sinon) zu spionieren, sondern einen Scheinspeicher zu verwenden und die ausgelösten Aktionen geltend zu machen. Dies hat den Vorteil, dass man mit Thunks umgehen kann, die Thunks entsenden, was mit Spionen nur sehr schwer zu bewerkstelligen ist.

Dies ist alles direkt aus den Dokumenten, aber lassen Sie es mich wissen, wenn Sie möchten, dass ich ein Beispiel für Ihren Thunk mache.

6
Michael Peyper

Für asynchrone Action-Ersteller, die Redux Thunk oder andere Middleware verwenden, sollten Sie den Redux-Shop für Tests vollständig verspotten. Sie können die Middleware mit redux-mock-store auf einen Mock Store anwenden. Um die HTTP-Anfrage zu simulieren, können Sie nock verwenden. 

Gemäß redux-mock-store Dokumentation müssen Sie am Ende der Anforderung store.getActions() aufrufen, um asynchrone Aktionen zu testen. Sie können Ihren Test so konfigurieren

mockStore(getState?: Object,Function) => store: Function Liefert ein Instanz des konfigurierten Modellspeichers. Wenn Sie Ihren Shop zurücksetzen möchten Nach jedem Test sollten Sie diese Funktion aufrufen.

store.dispatch(action) => action Liefert eine Aktion über die Scheingeschäft. Die Aktion wird in einem Array in der Instanz gespeichert und ausgeführt.

store.getState() => state: Object Gibt den Status des Modells zurück Geschäft

store.getActions() => actions: Array Gibt die Aktionen des Modells zurück Geschäft

store.clearActions() Löscht die gespeicherten Aktionen

Sie können die Testaktion gerne schreiben

import nock from 'nock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

//Configuring a mockStore
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

//Import your actions here
import {setLoading, setData, setFail} from '/path/to/actions';

test('test getSomeData', () => {
    const store = mockStore({});

    nock('http://datasource.com/', {
       reqheaders // you can optionally pass the headers here
    }).reply(200, yourMockResponseHere);

    const expectedActions = [
        setLoading(true),
        setData(yourMockResponseHere),
        setLoading(false)
    ];

    const dispatchedStore = store.dispatch(
        getSomeData()
    );
    return dispatchedStore.then(() => {
        expect(store.getActions()).toEqual(expectedActions);
    });
});

P.S. Behalten Sie im Auge, dass der Mock-Store sich nicht selbst aktualisiert, wenn die gespielte Aktion ausgelöst wird. Wenn Sie nach der vorherigen Aktion, die in der nächsten Aktion verwendet werden soll, von den aktualisierten Daten abhängig sind, müssen Sie Ihre eigene Instanz von schreiben es gefällt mir

const getMockStore = (actions) => {
    //action returns the sequence of actions fired and 
    // hence you can return the store values based the action
    if(typeof action[0] === 'undefined') {
         return {
             reducer: {isLoading: true}
         }
    } else {
        // loop over the actions here and implement what you need just like reducer

    }
}

und konfigurieren Sie dann die Variable mockStore

 const store = mockStore(getMockStore);

Ich hoffe es hilft. Überprüfen Sie auch this in der redux-Dokumentation zum Testen von Erstellern von asynchronen Aktionen 

5
Shubham Khatri

Wenn Sie die Abfertigungsfunktion mit jest.fn() verspotten, können Sie einfach auf dispatch.mock.calls zugreifen, um alle an Ihren Stub gerichteten Anrufe abzurufen.

  const dispatch = jest.fn();
  actions.yourAction()(dispatch);

  expect(dispatch.mock.calls.length).toBe(1);

  expect(dispatch.mock.calls[0]).toBe({
    type: SET_DATA,
    value: {...},
  });
1
Canastro

In meiner Antwort verwende ich axios anstelle von fetch, da ich nicht viel Erfahrung mit Abrufversprechen habe, dies sollte für Ihre Frage keine Rolle spielen. Ich persönlich fühle mich sehr wohl mit axios
Sehen Sie sich das Codebeispiel an, das ich unten bereitstelle:

// apiCalls.js
const fetchHelper = (url) => {
  return axios.get(url);
}


import * as apiCalls from './apiCalls'
describe('getSomeData', () => {
  it('should dispatch SET_LOADING_STATE on start of call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_LOADING_STATE,
      value: true,
    });
  });

  it('should dispatch SET_DATA action on successful api call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_DATA,
      value: { ...},
    });
  });

  it('should dispatch SET_FAIL action on failed api call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.reject());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_FAIL,
    });
  });
});

Hier verspottet ich den Abruf-Helfer, um zurückzukommen. Gelöstes Versprechen, den Erfolgsteil zu testen und das Versprechen abzulehnen, einen fehlgeschlagenen API-Aufruf zu testen. Sie können ihnen Argumente übergeben, um sie auch bei der Antwort zu überprüfen.
Sie können getSomeData folgendermaßen implementieren: 

const getSomeData = () => {
  return (dispatch) => {
    dispatch(setLoading(true));
    return fetchHelper('http://datasource.com/')
      .then(response => {
        dispatch(setData(response.data));
        dispatch(setLoading(false));
      })
      .catch(error => {
        dispatch(setFail());
        dispatch(setLoading(false));
      })
  }
}

Ich hoffe das löst dein Problem. Bitte kommentieren Sie, wenn Sie eine Klarstellung benötigen.
P.S Sie können sehen, wenn Sie sich den Code oben ansehen, warum ich Axios dem Abrufen vorziehen möchte, und erspart Ihnen viel Versprechen. 
Weitere Informationen dazu finden Sie unter: https://medium.com/@thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5

0
Swapnil