[Redux-Observable && Unit Testing] Use tests to verify updates to the Redux store (rxjs scheduler)

In certain situations, you care more about the final state of the redux store than you do about the particular stream of events coming out of an epic. In this lesson we explore a technique for dispatching actions direction into the store, having the epic execute as they would normally in production, and then assert on the updated store’s state.

 

To test a reducer, what we need to do is actually dispatch as action with its payload.

store.dispatch(action);

 

But before that, we need to get our 'store' configuration in the test.

configureStore.js:

复制代码
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducers';
import { ajax } from 'rxjs/observable/dom/ajax';

import {createEpicMiddleware} from 'redux-observable';
import {rootEpic} from "./epics/index";

export function configureStore(deps = {}) {
  const epicMiddleware = createEpicMiddleware(rootEpic, {
    dependencies: {
      ajax,
      ...deps
    }
  });

  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

  return createStore(
    reducer,
    composeEnhancers(
      applyMiddleware(epicMiddleware)
    )
  );
}
复制代码

index.js:

复制代码
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import {configureStore} from "./configureStore";

const store = configureStore();

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>
  , document.getElementById('root'));
复制代码

 

The configureStore.js exports function which create a store, we can import normally in the test file.

 

Now for example, we want to dispatch this action:

export function searchBeers(query) {
  return {
    type: SEARCHED_BEERS,
    payload: query
  }
}

Epic:

复制代码
import {Observable} from 'rxjs';
import {combineEpics} from 'redux-observable';
import {CANCEL_SEARCH, receiveBeers, searchBeersError, searchBeersLoading, SEARCHED_BEERS} from "../actions/index";

const beers  = `https://api.punkapi.com/v2/beers`;
const search = (term) => `${beers}?beer_name=${encodeURIComponent(term)}`;

export function searchBeersEpic(action$, store, deps) {
  return action$.ofType(SEARCHED_BEERS)
    .debounceTime(500)
    .filter(action => action.payload !== '')
    .switchMap(({payload}) => {

      // loading state in UI
      const loading = Observable.of(searchBeersLoading(true));

      // external API call
      const request = deps.ajax.getJSON(search(payload))
        .takeUntil(action$.ofType(CANCEL_SEARCH))
        .map(receiveBeers)
        .catch(err => {
          return Observable.of(searchBeersError(err));
        });

      return Observable.concat(
        loading,
        request,
      );
    })
}

export const rootEpic = combineEpics(searchBeersEpic);
复制代码

'decountTime' make the Epic async!

 

To verifiy the result is correct, we can do

  const store = configureStore(deps);

  const action = searchBeers('name');

  store.dispatch(action);

  expect(store.getState().beers.length).toBe(1);

BUT, actually this test code won't work, because the 'decountTime' in the epic, makes it as async opreation. Reducer expects everything happens sync...

 

One way can test it by using 'scheduler' from rxjs.

复制代码
import {Observable} from 'rxjs';
import {VirtualTimeScheduler} from 'rxjs/scheduler/VirtualTimeScheduler';
import {searchBeers} from "../actions/index";
import {configureStore} from "../configureStore";

it('should perform a search (redux)', function () {

  const scheduler = new VirtualTimeScheduler();
  const deps = {
    scheduler,
    ajax: {
      getJSON: () => Observable.of([{name: 'shane'}])
    }
  };

  const store = configureStore(deps);

  const action = searchBeers('shane');

  store.dispatch(action);

  scheduler.flush();

  expect(store.getState().beers.length).toBe(1);
});
复制代码

 

And we need to modifiy the epic:

.debounceTime(500, deps.scheduler)

 

Take away, we can test async oprations by using 'scheduler' from rxjs. 

-------------------FUll Code------------

Github

posted @   Zhentiw  阅读(616)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示