Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Test Redux Saga with an easy plan.

License

NotificationsYou must be signed in to change notification settings

jfairbank/redux-saga-test-plan

Repository files navigation

npmTravis branchCodecov

Test Redux Saga with an easy plan.

Redux Saga Test Plan makes testing sagas a breeze. Whether you need to testexact effects and their ordering or just test your sagaput's a specificaction at some point, Redux Saga Test Plan has you covered.

Redux Saga Test Plan aims to embrace both integration testing and unit testingapproaches to make testing your sagas easy.

Table of Contents

Documentation

Integration Testing

Requires globalPromise to be available

One downside to unit testing sagas is that it couples your test to yourimplementation. Simple reordering of yielded effects in your saga could breakyour tests even if the functionality stays the same. If you're not concernedwith the order or exact effects your saga yields, then you can take anintegrative approach, testing the behavior of your saga when run by Redux Saga.Then, you can simply test that a particular effect was yielded during the sagarun. For this, use theexpectSaga test function.

Simple Example

Import theexpectSaga function and pass in your saga function as an argument.Any additional arguments toexpectSaga will become arguments to the sagafunction. The return value is a chainable API with assertions for the differenteffect creators available in Redux Saga.

In the example below, we test that theuserSaga successfullyputs aRECEIVE_USER action with thefakeUser as the payload. We callexpectSagawith theuserSaga and supply anapi object as an argument touserSaga. Weassert the expectedput effect via theput assertion method. Then, we callthedispatch method with aREQUEST_USER action that contains the user idpayload. Thedispatch method will supply actions totake effects. Finally,we start the test by calling therun method which returns aPromise. TestswithexpectSaga will always run asynchronously, so the returnedPromiseresolves when the saga finishes or whenexpectSaga forces a timeout. If you'reusing a test runner like Jest, you can return thePromise inside your Jesttest so Jest knows when the test is complete.

import{call,put,take}from'redux-saga/effects';import{expectSaga}from'redux-saga-test-plan';function*userSaga(api){constaction=yieldtake('REQUEST_USER');constuser=yieldcall(api.fetchUser,action.payload);yieldput({type:'RECEIVE_USER',payload:user});}it('just works!',()=>{constapi={fetchUser:id=>({ id,name:'Tucker'}),};returnexpectSaga(userSaga,api)// Assert that the `put` will eventually happen..put({type:'RECEIVE_USER',payload:{id:42,name:'Tucker'},})// Dispatch any actions that the saga will `take`..dispatch({type:'REQUEST_USER',payload:42})// Start the test. Returns a Promise..run();});

Mocking with Providers

expectSaga runs your saga with Redux Saga, so it will try to resolve effectsjust like Redux Saga would in your application. This is great for integrationtesting, but sometimes it can be laborious to bootstrap your entire applicationfor tests or mock things like server APIs. In those cases, you can useproviders which are perfect for mocking values directly withexpectSaga.Providers are similar to middleware that allow you to intercept effects beforethey reach Redux Saga. You can choose to return a mock value instead of allowingRedux Saga to handle the effect, or you can pass on the effect to otherproviders or eventually Redux Saga.

expectSaga has two flavors of providers,static providers anddynamicproviders. Static providers are easier to compose and reuse, but dynamicproviders give you more flexibility with non-deterministic effects. Here is oneexample below using static providers. There are more examples of providersinthedocs.

import{call,put,take}from'redux-saga/effects';import{expectSaga}from'redux-saga-test-plan';import*asmatchersfrom'redux-saga-test-plan/matchers';import{throwError}from'redux-saga-test-plan/providers';importapifrom'my-api';function*userSaga(api){try{constaction=yieldtake('REQUEST_USER');constuser=yieldcall(api.fetchUser,action.payload);constpet=yieldcall(api.fetchPet,user.petId);yieldput({type:'RECEIVE_USER',payload:{ user, pet},});}catch(e){yieldput({type:'FAIL_USER',error:e});}}it('fetches the user',()=>{constfakeUser={name:'Jeremy',petId:20};constfakeDog={name:'Tucker'};returnexpectSaga(userSaga,api).provide([[call(api.fetchUser,42),fakeUser],[matchers.call.fn(api.fetchPet),fakeDog],]).put({type:'RECEIVE_USER',payload:{user:fakeUser,pet:fakeDog},}).dispatch({type:'REQUEST_USER',payload:42}).run();});it('handles errors',()=>{consterror=newError('error');returnexpectSaga(userSaga,api).provide([[matchers.call.fn(api.fetchUser),throwError(error)]]).put({type:'FAIL_USER', error}).dispatch({type:'REQUEST_USER',payload:42}).run();});

Notice we pass in an array of tuple pairs (or array pairs) that contain amatcher and a fake value. You can use the effect creators from Redux Saga ormatchers from theredux-saga-test-plan/matchers module to match effects. Thebonus of using Redux Saga Test Plan's matchers is that they offer specialpartial matchers likecall.fn which matches by the function without worryingabout the specificargs contained in the actualcall effect. Notice in thesecond test that we can also simulate errors with thethrowError function fromtheredux-saga-test-plan/providers module. This is perfect for simulatingserver problems.

Example with Reducer

One good use case for integration testing is testing your reducer too. You canhook up your reducer to your test by calling thewithReducer method with yourreducer function.

import{put}from'redux-saga/effects';import{expectSaga}from'redux-saga-test-plan';constinitialDog={name:'Tucker',age:11,};functionreducer(state=initialDog,action){if(action.type==='HAVE_BIRTHDAY'){return{      ...state,age:state.age+1,};}returnstate;}function*saga(){yieldput({type:'HAVE_BIRTHDAY'});}it('handles reducers and store state',()=>{returnexpectSaga(saga).withReducer(reducer).hasFinalState({name:'Tucker',age:12,// <-- age changes in store state}).run();});

Unit Testing

If you want to ensure that your saga yields specific types of effects in aparticular order, then you can use thetestSaga function. Here's a simpleexample:

import{testSaga}from'redux-saga-test-plan';functionidentity(value){returnvalue;}function*mainSaga(x,y){constaction=yieldtake('HELLO');yieldput({type:'ADD',payload:x+y});yieldcall(identity,action);}constaction={type:'TEST'};it('works with unit tests',()=>{testSaga(mainSaga,40,2)// advance saga with `next()`.next()// assert that the saga yields `take` with `'HELLO'` as type.take('HELLO')// pass back in a value to a saga after it yields.next(action)// assert that the saga yields `put` with the expected action.put({type:'ADD',payload:42}).next()// assert that the saga yields a `call` to `identity` with// the `action` argument.call(identity,action).next()// assert that the saga is finished.isDone();});

Extending inspect options

To see large effect objects while Expected & Actual result comparison you'll need to extend inspect options. Example:

importutilfrom'util';importtestSagafrom'redux-saga-test-plan';import{testableSaga}from'../sagas';describe('Some sagas to test',()=>{util.inspect.defaultOptions.depth=null;it('testableSaga',()=>{testSaga(testableSaga).next().put({/* large object here */}).next().isDone();});});

Install

yarn add redux-saga-test-plan --dev
npm install --save-dev redux-saga-test-plan

[8]ページ先頭

©2009-2025 Movatter.jp