• Stars
    star
    227
  • Rank 169,539 (Top 4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 7 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Complete and opinionated testkit for testing Redux projects (reducers, selectors, actions, thunks)

Redux Testkit

Complete and opinionated testkit for testing Redux projects (reducers, selectors, actions, thunks)


What tests are we going to write?

This library mostly provides syntactic sugar and makes testing Redux fun with less boilerplate. You can naturally test all Redux constructs without this library, but you will miss out on features like automatic immutability checks.


Installation

  • Install the package from npm
npm install redux-testkit --save-dev
  • Make sure you have a test runner installed, we recommend jest
npm install jest --save-dev

Recipe - Unit testing reducers

import { Reducer } from 'redux-testkit';
import uut from '../reducer';

describe('counter reducer', () => {

  it('should have initial state', () => {
    expect(uut()).toEqual({ counter: 0 });
  });

  it('should handle INCREMENT action on initial state', () => {
    const action = { type: 'INCREMENT' };
    const result = { counter: 1 };
    Reducer(uut).expect(action).toReturnState(result);
  });

  it('should handle INCREMENT action on existing state', () => {
    const action = { type: 'INCREMENT' };
    const state = { counter: 1 };
    const result = { counter: 2 };
    Reducer(uut).withState(state).expect(action).toReturnState(result);
  });

  it('should return the same state after accpeting a non existing action', () => {
    const action = { type: 'NON_EXISTING' };
    const state = { counter: 1 };
    Reducer(uut).withState(state).expect(action).toStayTheSame();
  });

});

A redux reducer is a pure function that takes an action object, with a type field, and changes the state. In almost every case the state object itself must remain immutable.

Reducer(reducer).withState(state).expect(action).toReturnState(result)

  • Runs the reducer on current state providing an action. Calling withState() is optional, if not provided, initial state is used. Makes sure the returned state is result.

  • Also verifies immutability - that state did not mutate. Why is this important? see example bug

See some examples of this API

Reducer(reducer).withState(state).expect(action).toStayTheSame()

  • Runs the reducer on current state providing an action. Calling withState() is optional, if not provided, initial state is used. Makes sure the returned state is the same as the provided state.

Reducer(reducer).withState(state).expect(action).toChangeInState(changes)

  • Runs the reducer on current state providing an action. Calling withState() is optional, if not provided, initial state is used. Makes sure the part that changed in the returned state matches changes and the rest of the state hasn't changed. The format of changes is partial state, even a deep internal object - it is compared to returned state after merging the changes with the original state (objects are deep merged, arrays are replaced).

  • Also verifies immutability of the state.

The added value of this API compared to toReturnState is when your state object is very large and you prefer to reduce the boilerplate of preparing the entire result by yourself.

See some examples of this API

Reducer(reducer).withState(state).execute(action)

  • Runs the reducer on current state providing an action. Calling withState() is optional, if not provided, initial state is used.

  • Returns the returned state so you can run expectations manually. It's not recommended to use this API directly because you usually won't verify that parts in the returned state that were not supposed to change, indeed did not change.

  • Also verifies immutability of the state.

The added value of this API compared to the others is that it allows you to run your own custom expectations (which isn't recommended).

See some examples of this API


Recipe - Unit testing selectors

import { Selector } from 'redux-testkit';
import * as uut from '../reducer';

describe('numbers selectors', () => {

  it('should select integers from numbers state', () => {
    const state = { numbers: [1, 2.2, 3.14, 4, 5.75, 6] };
    const result = [1, 4, 6];
    Selector(uut.getIntegers).expect(state).toReturn(result);
  });

});

A redux selector is a pure function that takes the state and computes some derivation from it. This operation is read-only and the state object itself must not change.

Selector(selector).expect(state, ...args).toReturn(result)

  • Runs the selector function on a given state. If the selector takes more arguments, provide them at ...args (the state is always assumed to be the first argument of a selector). Makes sure the returned result is result.

  • Also verifies that state did not mutate. Why is this important? see example bug

See some examples of this API

Selector(selector).execute(state, ...args)

  • Runs the selector function on a given state. If the selector takes more arguments, provide them at ...args (the state is always assumed to be the first argument of a selector).

  • Returns the returned state so you can run expectations manually.

  • Also verifies that state did not mutate.

The added value of this API compared to the others is that it allows you to run your own custom expectations.

See some examples of this API


Recipe - Unit testing thunks

import { Thunk } from 'redux-testkit';
import * as uut from '../actions';
import redditService from '../../../services/reddit';
jest.mock('../../../services/reddit');

describe('posts actions', () => {

  beforeEach(() => {
    jest.resetAllMocks();
  });

  it('should clear all posts', () => {
    const dispatches = Thunk(uut.clearPosts).execute();
    expect(dispatches.length).toBe(1);
    expect(dispatches[0].getAction()).toEqual({ type: 'POSTS_UPDATED', posts: [] });
  });

  it('should fetch posts from server', async () => {
    redditService.getPostsBySubreddit.mockReturnValueOnce(['post1', 'post2']);
    const dispatches = await Thunk(uut.fetchPosts).execute();
    expect(dispatches.length).toBe(3);
    expect(dispatches[0].getAction()).toEqual({ type: 'POSTS_LOADING', loading: true });
    expect(dispatches[1].getAction()).toEqual({ type: 'POSTS_UPDATED', posts: ['post1', 'post2'] });
    expect(dispatches[2].getAction()).toEqual({ type: 'POSTS_LOADING', loading: false });
  });

  it('should filter posts', () => {
    const state = { loading: false, posts: ['funny1', 'scary2', 'funny3'] };
    const dispatches = Thunk(uut.filterPosts).withState(state).execute('funny');
    expect(dispatches.length).toBe(1);
    expect(dispatches[0].getAction()).toEqual({ type: 'POSTS_UPDATED', posts: ['funny1', 'funny3'] });
  });

});

A redux thunk wraps a synchronous or asynchronous function that performs an action. It can dispatch other actions (either plain objects or other thunks). It can also perform side effects like accessing servers.

Thunk(thunk).withState(state).execute(...args)

  • Runs the thunk thunk on current state given optional arguments ...args. Calling withState() is optional, no need to provide it if the internal thunk implementation doesn't call getState().

  • Returns an array of dispatches performed by the thunk (shallow, these dispatches are not executed). You can run expectations over them manually. If the tested thunk is asynchronous, await on the result to get the actual dispatches array. If it's synchronous, you can use the return value directly without await.

  • Also verifies that state did not mutate. Why is this important? see example bug

See some examples of this API

Available expectations over a dispatch
// when a plain object action was dispatched
expect(dispatches[0].isPlainObject()).toBe(true);
expect(dispatches[0].getType()).toEqual('LOADING_CHANGED');
expect(dispatches[0].getAction()).toEqual({ type: 'LOADING_CHANGED', loading: true });

// when another thunk was dispatched
expect(dispatches[0].isFunction()).toBe(true);
expect(dispatches[0].getName()).toEqual('refreshSession'); // the function name, see note below
Being able to expect dispatched thunk function names

This is relevant when the tested thunk dispatches another thunk. In order to be able to test the name of the thunk that was dispatched, you will have to provide an explicit name to the internal anonymous function in the thunk implementation. For example:

export function refreshSession() {
  return async function refreshSession(dispatch, getState) {
    // ...
  };
}
Limitations when testing thunks that dispatch other thunks
  • If the tested thunk dispatches another thunk, the other thunk is not executed. Different thunks should be considered as different units. Executing another unit should be part of an integration test, not a unit test.

  • If the tested thunk dispatches another thunk, you cannot set expectations on the arguments given to the other thunk. Different thunks should be considered as different units. Testing the interfaces between them should be part of an integration test, not a unit test.

  • If the tested thunk dispatches another thunk, you cannot mock the return value of the other thunk. Relying in your implementation on the return value of another thunk is considered bad practice. If you must test that, you should probably be changing your implementation.

These limitations may seem annoying, but they stem from best practices. If they disrupt your test, it's usually a sign of a code smell in your implementation. Fix the implementation, don't fight to test a bad practice.


Recipe - Integration tests for the entire store

import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { FlushThunks } from 'redux-testkit';

import * as reducers from '../reducers';
import * as uut from '../posts/actions';
import * as postsSelectors from '../posts/reducer';
import redditService from '../../services/reddit';
jest.mock('../../services/reddit');

describe('posts store integration', () => {

  let flushThunks, store;

  beforeEach(() => {
    jest.resetAllMocks();
    // create a redux store with flushThunks added as the first middleware
    flushThunks = FlushThunks.createMiddleware();
    store = createStore(combineReducers(reducers), applyMiddleware(flushThunks, thunk));
  });

  it('should select posts', () => {
    expect(postsSelectors.getSelectedPost(store.getState())).toEqual([]);
    store.dispatch(uut.selectPost('post1'));
    expect(postsSelectors.getSelectedPost(store.getState())).toEqual(['post1']);
    store.dispatch(uut.selectPost('post2'));
    expect(postsSelectors.getSelectedPost(store.getState())).toEqual(['post1', 'post2']);
  });

  it('should fetch posts from server', async () => {
    redditService.getPostsBySubreddit.mockReturnValueOnce(['post1', 'post2']);
    expect(postsSelectors.getPostsLoading(store.getState())).toBe(false);
    expect(postsSelectors.getPosts(store.getState())).toEqual([]);
    await store.dispatch(uut.fetchPosts());
    expect(postsSelectors.getPostsLoading(store.getState())).toBe(false);
    expect(postsSelectors.getPosts(store.getState())).toEqual(['post1', 'post2']);
  });

  it('should test a thunk that dispatches another thunk', async () => {
    expect(postsSelectors.isForeground(store.getState())).toBe(false);
    await store.dispatch(uut.initApp()); // this dispathces thunk appOnForeground
    await flushThunks.flush(); // wait until all async thunks resolve
    expect(postsSelectors.isForeground(store.getState())).toBe(true);
  });

});

Integration test for the entire store creates a real redux store with an extra flushThunks middleware. Test starts by dispatching an action / thunk. Expectations are set over the final state using selectors.

flushThunks = FlushThunks.createMiddleware()

  • Creates flushThunks middleware which should be applied to the store on creation. This middleware is useful for the case where one thunk dispatches another thunk. It allows to wait until all of the thunk promises have been resolved.

  • Returns a flushThunks instance which has the following methods:

flushThunks.flush()
  • Flushes all asynchronous thunks. Run await on this method to wait until all dispatched thunk promises are resolved.
flushThunks.reset()
  • Call this method to reset the list of thunk promises observed by flushThunks.

Building and testing this library

This section is relevant only if you want to contribute to this library or build it locally.

  • Install and build
npm install
npm run build
  • Run lint and tests
npm run test

License

MIT

More Repositories

1

react-native-interactable

Experimental implementation of high performance interactable views in React Native
JavaScript
5,171
star
2

react-templates

Light weight templates for react
JavaScript
2,813
star
3

vscode-glean

The extension provides refactoring tools for your React codebase
TypeScript
1,455
star
4

mjml-react

React component library to generate the HTML emails on the fly
JavaScript
980
star
5

react-native-zss-rich-text-editor

React Native rich text editor based on ZSSRichTextEditor
HTML
838
star
6

react-native-keyboard-input

Use your own custom input component instead of the system keyboard
Objective-C
797
star
7

wml

An alternative to symlinks that actually copies changed files from source to destination folders
JavaScript
769
star
8

angular-tree-control

Angular JS Tree
HTML
709
star
9

DetoxInstruments

Detox Instruments is a performance–analysis and testing framework, designed to help developers profile their mobile apps in order to better understand and optimize their app's behavior and performance.
Objective-C
621
star
10

react-native-controllers

Native IOS Navigation for React Native (navbar, tabs, drawer)
Objective-C
610
star
11

react-native-autogrow-textinput

Objective-C
540
star
12

accord

Accord: A sane validation library for Scala
Scala
534
star
13

react-native-crash-course

The React Native crash course by Wix is a self-learning course designed to help you learn everything you need to know before diving into writing production code in React Native.
JavaScript
525
star
14

react-native-keyboard-aware-scrollview

Created by artald
JavaScript
485
star
15

wix-embedded-mysql

embedded mysql based on https://github.com/flapdoodle-oss/de.flapdoodle.embed.process
Java
379
star
16

DetoxRecorder

Detox Recorder is a utility for recordings steps for a Detox test as you use your app in Simulator. After recording the test, add expectations that check if interface elements are in the expected state.
Objective-C
286
star
17

react-dataflow-example

Experimenting with different dataflow approaches for a real life React app
JavaScript
279
star
18

eslint-plugin-lodash

ESLint rules for lodash
JavaScript
272
star
19

pro-gallery

Blazing fast & beautiful galleries built for the web
JavaScript
265
star
20

quix

Quix Notebook Manager
TypeScript
265
star
21

petri

Wix experiment system (A/B test and feature toggle framework)
JavaScript
264
star
22

redux-saga-tester

Full redux environment testing helper for redux-saga
JavaScript
249
star
23

react-native-wordpress-editor

React Native Wrapper for WordPress Rich Text Editor
Objective-C
246
star
24

remx

Opinionated mobx
JavaScript
221
star
25

exodus

Easily migrate your JVM code from Maven to Bazel
Scala
221
star
26

react-native-action-view

An easy to use component that allows displaying swipeable buttons with a variety of transitions.
Objective-C
187
star
27

tdd-katas

TDD katas
JavaScript
175
star
28

react-native-custom-segmented-control

Custom version of the IOS SegmentedControl component
Objective-C
168
star
29

repluggable

Pluggable micro frontends in React+Redux apps
TypeScript
166
star
30

lerna-script

Lerna addon for adding custom tasks
JavaScript
164
star
31

react-native-wix-engine

Java
164
star
32

angular-widget

Lazy load isolated micro-apps in Angular
JavaScript
163
star
33

angular-viewport-watch

Angular directive that disables watchers in scopes out of viewport
JavaScript
148
star
34

react-native-keyboard-tracking-view

Objective-C
134
star
35

kampos

Tiny and fast effects compositor on WebGL
JavaScript
129
star
36

react-native-swipe-view

Native container for a React Native view which supports swipe behavior (for swipe to delete and such)
Java
124
star
37

list-view-experiments

React Native ListView Experiments
Objective-C
123
star
38

rn-perf-experiments

Various performance experiments with React Native over a swipeable card pattern
JavaScript
119
star
39

rn-perf-experiments2

React Native performance experiments revisited
JavaScript
115
star
40

codio

A media format to record and playback the process of programming
Kotlin
105
star
41

rn-synchronous-render

Experiments with synchronous rendering in React Native
Objective-C
104
star
42

react-native-gifted-chat

JavaScript
99
star
43

as-typed

Ambient mapping from JSON schema to typescript
TypeScript
98
star
44

playable

No hassle, no fuss, just nice and easy video player
TypeScript
95
star
45

tspoon

(DEPRECATED) AST visitors for TypeScript
TypeScript
83
star
46

remote-dom

JavaScript
82
star
47

react-native-paged-contacts

Paged contacts for React Native
Java
80
star
48

react-native-newrelic

New Relic reporting for React Native
JavaScript
79
star
49

obsidian

Dependency injection library for React and React Native applications
TypeScript
74
star
50

react-module-container

Small library for building micro apps in React and Angular
JavaScript
68
star
51

react-native-repackager

Custom extensions for react-native packager
JavaScript
67
star
52

rapido

πŸƒ A site performance test kit, built using Chrome's DevTools.
JavaScript
63
star
53

BindingListView

React Native ListView experimental implementation supporting direct view binding
Objective-C
62
star
54

rawss

Generic CSS polyfill framework, with a CSS variables implementation
TypeScript
59
star
55

wix-animations

Tools for easy and simple animating capabilities
JavaScript
59
star
56

haste

An extendable, blazing fast build system that cares about user experience
JavaScript
58
star
57

kafka-connect-s3

A Kafka-Connect Sink for S3 with no Hadoop dependencies.
Java
56
star
58

unidriver

UniDriver - Universal Component Drivers πŸš€
TypeScript
53
star
59

carmi

CARMI - Compiler for Automatic Reactive Modelling of Inference
JavaScript
49
star
60

mobile-crash-course

Crash course for engineers in Wix to start working on the mobile stack
49
star
61

react-native-extended-cli

Extended CLI with convenient scripts and utilities for developing React Native apps
Shell
47
star
62

protractor-helpers

Set of matchers, locators, and helper functions for Protractor
JavaScript
46
star
63

Kompot

Component testing for React Native using Detox
JavaScript
45
star
64

future-perfect

A helper for Futures covering non-functional concerns such as retries, timeouts and reporting
Scala
44
star
65

mjml-react-example

mjml-react example project
JavaScript
42
star
66

specs2-jmock

This is a specs2 adapter + DSL for using the popular mocking framework JMock
Scala
41
star
67

corvid

Download your Wix site, code in a local IDE, collaborate, use git, and more!
JavaScript
39
star
68

javascript-essentials

Essential reading and learning materials for Wix client-side engineers
37
star
69

fed-training-kit

A self-guided onboarding kit for new FED Guild members
JavaScript
35
star
70

commons-validator-js

JavaScript port of Apache Commons Validator
JavaScript
35
star
71

DeviantArt-API

The DeviantArt API
35
star
72

redux-cornell

A Redux library which helps reduce boilerplate
TypeScript
34
star
73

wix-http-testkit

Tools for testing HTTP services
Scala
34
star
74

DetoxSync

Synchronization framework for Detox and other testing frameworks
Objective-C
34
star
75

enzyme-drivers

Enzyme Drivers is a JavaScript library that makes react component testing with enzyme a lot easier and fun to write
JavaScript
33
star
76

mutable

State containers with dirty checking and more
JavaScript
32
star
77

eyes.it

Add screenshot comparison to existing protractor tests simply by changing `it` to `eyes.it`
JavaScript
31
star
78

react-native-sane-listview

Why do we need all this datasource nonsense?!
JavaScript
31
star
79

wix-ui-backoffice

Common React UI components for all Wix backoffice React applications
TypeScript
31
star
80

turnerjs

An angular test kit for components and directives. See:
TypeScript
29
star
81

zorechka-bot

Github bot for keeping your Bazel dependencies up-to-date and clean
Scala
27
star
82

Koboshi

Obsessed with your precious data
Scala
27
star
83

react-popup-manager

Manage react popups, Modals, Lightboxes, Notifications
TypeScript
26
star
84

sample-wix-rest-app

JavaScript
24
star
85

wix-react-native-storybook-template

Server to host storybook for react native apps
JavaScript
24
star
86

wix-code-docs

Wix Code Reference Documentation - docworks jsons
JavaScript
24
star
87

hello-react-templates

Starter project for react-templates
JavaScript
24
star
88

react-native-open-file

Objective-C
24
star
89

isolated-runtime

Run untrusted Javascript code in a multi-tenant, isolated environment
JavaScript
23
star
90

react-hoverbox

Created by malsomnus
JavaScript
23
star
91

react-sequence-animator

A React library for sequence animations
TypeScript
23
star
92

fast-boot

Caching of the FS location of node modules between node process startups
JavaScript
22
star
93

wixmadefor

Wix Madefor font
Python
22
star
94

svg2react-icon

Generate React icon components from SVG raw files
JavaScript
22
star
95

quick-2fa

Safely generate two-factor authentication tokens into clipboard
JavaScript
21
star
96

async-graph-resolver

A Utility to handle execution and aggregation of async actions
TypeScript
21
star
97

DetoxIPC

DetoxIPC is an asynchronous, bi-directional inter-process remote invocation library for Apple platforms with an API similar to Apple's NSXPCConnection.
Objective-C
21
star
98

react-native-animation-library

JavaScript
20
star
99

data-capsule

A pluggable capsule for storing key/value data for your application
TypeScript
20
star
100

DTXLoggingInfra

Logging infrastructure for Apple platforms
Objective-C
18
star