web-dev-qa-db-fra.com

Comment tester à l'unité ce thunk Redux?

J'ai donc ce créateur d'action Redux qui utilise redux thunk middleware:

accountDetailsActions.js:

export function updateProduct(product) {
  return (dispatch, getState) => {
    const { accountDetails } = getState();

    dispatch({
      type: types.UPDATE_PRODUCT,
      stateOfResidence: accountDetails.stateOfResidence,
      product,
    });
  };
}

Comment le tester? J'utilise le package chai pour les tests. J'ai trouvé des ressources en ligne, mais je ne sais pas comment procéder. Voici mon test jusqu'à présent:

accountDetailsReducer.test.js:

describe('types.UPDATE_PRODUCT', () => {
    it('should update product when passed a product object', () => {
        //arrange
        const initialState = {
            product: {}
        };
        const product = {
            id: 1,
            accountTypeId: 1,
            officeRangeId: 1,
            additionalInfo: "",
            enabled: true
        };
        const action = actions.updateProduct(product);
        const store = mockStore({courses: []}, action);
        store.dispatch(action);
        //this is as far as I've gotten - how can I populate my newState variable in order to test the `product` field after running the thunk?
        //act
        const newState = accountDetailsReducer(initialState, action);
        //assert
        expect(newState.product).to.be.an('object');
        expect(newState.product).to.equal(product);
    });
});

Mon thunk ne fait aucune action asynchrone. Aucun conseil?

12
twilco

Comment tester unitairement les Thunks Redux

L'intérêt d'un créateur d'action thunk est d'envoyer des actions asynchrones à l'avenir. Lorsque vous utilisez redux-thunk, une bonne approche consiste à modéliser le flux asynchrone de début et de fin, ce qui entraîne un succès ou une erreur avec trois actions.

Bien que cet exemple utilise Mocha et Chai pour les tests, vous pouvez tout aussi facilement utiliser n'importe quelle bibliothèque d'assertions ou framework de test.

Modélisation du processus asynchrone avec plusieurs actions gérées par notre principal créateur d'action thunk

Supposons pour cet exemple que vous souhaitiez effectuer une opération asynchrone qui met à jour un produit et que vous souhaitiez connaître trois choses cruciales.

  • Quand l'opération asynchrone commence
  • Une fois l'opération asynchrone terminée
  • Si l'opération asynchrone a réussi ou échoué

Bon alors il est temps de modéliser nos actions redux en fonction de ces étapes du cycle de vie de l'opération. N'oubliez pas que la même chose s'applique à toutes les opérations asynchrones, ce qui serait généralement appliqué aux requêtes http pour récupérer des données à partir d'une API.

Nous pouvons écrire nos actions comme ça.

accountDetailsActions.js:

export function updateProductStarted (product) {
  return {
    type: 'UPDATE_PRODUCT_STARTED',
    product,
    stateOfResidence
  }
}

export function updateProductSuccessful (product, stateOfResidence, timeTaken) {
  return {
    type: 'PRODUCT_UPDATE_SUCCESSFUL',
    product,
    stateOfResidence
    timeTaken
  }
}

export function updateProductFailure (product, err) {
  return {
    product,
    stateOfResidence,
    err
  }
}

// our thunk action creator which dispatches the actions above asynchronously
export function updateProduct(product) {
  return dispatch => {
    const { accountDetails } = getState()
    const stateOfResidence = accountDetails.stateOfResidence

    // dispatch action as the async process has begun
    dispatch(updateProductStarted(product, stateOfResidence))

    return updateUser()
        .then(timeTaken => {
           dispatch(updateProductSuccessful(product, stateOfResidence, timeTaken)) 
        // Yay! dispatch action because it worked
      }
    })
    .catch(error => {
       // if our updateUser function ever rejected - currently never does -
       // oh no! dispatch action because of error
       dispatch(updateProductFailure(product, error))

    })
  }
}

Notez l'action à la recherche occupée en bas. C'est notre créateur d'action thunk. Puisqu'il renvoie une fonction, c'est une action spéciale qui est interceptée par un middleware redux-thunk. Ce créateur d'action de thunk peut envoyer les autres créateurs d'action à un moment donné dans le futur. Assez intelligent.

Nous avons maintenant écrit les actions pour modéliser un processus asynchrone qui est une mise à jour utilisateur. Disons que ce processus est un appel de fonction qui renvoie une promesse, comme ce serait l'approche la plus courante aujourd'hui pour traiter les processus asynchrones.

Définir la logique de l'opération asynchrone réelle que nous modélisons avec des actions redux

Pour cet exemple, nous allons simplement créer une fonction générique qui renvoie une promesse. Remplacez-le par la fonction réelle qui met à jour les utilisateurs ou effectue la logique asynchrone. Assurez-vous que la fonction renvoie une promesse.

Nous utiliserons la fonction définie ci-dessous afin de créer un exemple autonome de travail. Pour obtenir un exemple de travail, il suffit de lancer cette fonction dans votre fichier d'actions afin qu'elle soit à la portée de votre créateur d'action thunk.

 // This is only an example to create asynchronism and record time taken
 function updateUser(){
      return new Promise( // Returns a promise will be fulfilled after a random interval
          function(resolve, reject) {
              window.setTimeout(
                  function() {
                      // We fulfill the promise with the time taken to fulfill
                      resolve(thisPromiseCount);
                  }, Math.random() * 2000 + 1000);
          }
      )
})

Notre fichier de test

import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import chai from 'chai' // You can use any testing library
let expect = chai.expect;

import { updateProduct } from './accountDetailsActions.js'

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

describe('Test thunk action creator', () => {
  it('expected actions should be dispatched on successful request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'updateProductStarted', 
        'updateProductSuccessful'
    ]

    return store.dispatch(fetchSomething())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).to.eql(expectedActions)
     })

  })

  it('expected actions should be dispatched on failed request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'updateProductStarted', 
        'updateProductFailure'
    ]

    return store.dispatch(fetchSomething())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).to.eql(expectedActions)
     })

  })
})
12
therewillbecode

Jetez un oeil à Recette: Écriture de tests de la documentation officielle. Aussi, que testez-vous, le créateur d'action ou le réducteur?

Exemple de test du créateur d'actions

describe('types.UPDATE_PRODUCT', () => {
    it('should update product when passed a product object', () => {    
        const store = mockStore({courses: []});
        const expectedActions = [
            / * your expected actions */
        ];

        return store.dispatch(actions.updateProduct(product))
            .then(() => {
                expect(store.getActions()).to.eql(expectedActions);
            });
    });
});

Exemple de test de réduction

Votre réducteur doit être une fonction pure, vous pouvez donc le tester isolément en dehors de l'environnement du magasin.

const yourReducer = require('../reducers/your-reducer');

describe('reducer test', () => {
    it('should do things', () => {
        const initialState = {
            product: {}
        };

        const action = {
            type: types.UPDATE_PRODUCT,
            stateOfResidence: // whatever values you want to test with,
            product: {
                id: 1,
                accountTypeId: 1,
                officeRangeId: 1,
                additionalInfo: "",
                enabled: true
            }
        }

        const nextState = yourReducer(initialState, action);

        expect(nextState).to.be.eql({ /* ... */ });
    });
});
6
Mario Tacke
export const someAsyncAction = (param) => (dispatch, getState) => {
    const { mock } = getState();
    dispatch({
        type: 'SOME_TYPE',
        mock: mock + param,
    })
}

it('should test someAsyncAction', () => {
    const param = ' something';
    const dispatch = jest.fn().mockImplementation();
    const getState = () => ({
        mock: 'mock value',
    });

    const expectedAction = {
        type: 'SOME_TYPE',
        mock: 'mock value something'
    };

    const callback = someAsyncAction(param);
    expect(typeof callback).toBe('function');

    callback.call(this, dispatch, getState);
    expect(dispatch.mock.calls[0]).toEqual([expectedAction])
});
0
Krystian Mężyk