Testing
Rematch testing works out of the box with many libraries like Jest, Mocha, Ava, and of course works fine with end to end tests like Cypress or Testing Library.
tip
You can check our full suite of test of @rematch/core to check examples of how to test. We're using jest
but they should work with any testing provider.
Jest​
This tests are based in Jest, but should be pretty similar in other testing frameworks.
Reducers​
Testing with store.
ts
import {init } from "@rematch/core";import {models ,RootModel } from "./models";Âdescribe ("[count] model", () => {it ("incrementAsync effect should increment given a payload", async () => {conststore =init <RootModel >({models ,});Âawaitstore .dispatch .count .incrementAsync (3);ÂconstmyModelData =store .getState ().count ;expect (myModelData ).toEqual (3);});});
ts
import {init } from "@rematch/core";import {models ,RootModel } from "./models";Âdescribe ("[count] model", () => {it ("incrementAsync effect should increment given a payload", async () => {conststore =init <RootModel >({models ,});Âawaitstore .dispatch .count .incrementAsync (3);ÂconstmyModelData =store .getState ().count ;expect (myModelData ).toEqual (3);});});
Effects​
Testing with store.
ts
import {init } from "@rematch/core";import {models ,RootModel } from "./models";Âdescribe ("[count] model", () => {it ("effect: my incrementAsync effect should do something", async () => {conststore =init <RootModel >({models ,});Âawaitstore .dispatch .count .incrementAsync (3);ÂconstcountData =store .getState ().count ;expect (countData ).toEqual (3);});});
ts
import {init } from "@rematch/core";import {models ,RootModel } from "./models";Âdescribe ("[count] model", () => {it ("effect: my incrementAsync effect should do something", async () => {conststore =init <RootModel >({models ,});Âawaitstore .dispatch .count .incrementAsync (3);ÂconstcountData =store .getState ().count ;expect (countData ).toEqual (3);});});
Testing effects directly.
ts
import {count } from "./count";Âdescribe ("myModel model", () => {it ("effect: my effectName should do something", async () => {constreducerMockFn =jest .fn ();Â// bind the functions you want to checkawait (count .effects as any).incrementAsync .call ({reducerThatIsGoingToBeCalled :reducerMockFn },{payload : "" });Â// checking if it was calledexpect (reducerMockFn ).toHaveBeenCalled ();Â// checking if it was called with the expected paramsexpect (reducerMockFn ).toHaveBeenCalledWith ("something");});});
ts
import {count } from "./count";Âdescribe ("myModel model", () => {it ("effect: my effectName should do something", async () => {constreducerMockFn =jest .fn ();Â// bind the functions you want to checkawait (count .effects as any).incrementAsync .call ({reducerThatIsGoingToBeCalled :reducerMockFn },{payload : "" });Â// checking if it was calledexpect (reducerMockFn ).toHaveBeenCalled ();Â// checking if it was called with the expected paramsexpect (reducerMockFn ).toHaveBeenCalledWith ("something");});});
Testing Library​
To get started with Testing Library tests since they're similar to e2e you have to wrap your React application with the React-Redux Provider:
testUtils.tsxtsx
importReact from "react"import {render } from "@testing-library/react"import {Provider } from "react-redux"import type {Store } from "redux"Âexport constrenderWithRematchStore = (ui :React .ReactElement ,store :Store ) =>render (ui , {wrapper : ({children }) => <Provider store ={store }>{children }</Provider >,})
testUtils.tsxtsx
importReact from "react"import {render } from "@testing-library/react"import {Provider } from "react-redux"import type {Store } from "redux"Âexport constrenderWithRematchStore = (ui :React .ReactElement ,store :Store ) =>render (ui , {wrapper : ({children }) => <Provider store ={store }>{children }</Provider >,})
Now, we can just use this renderWithRemathStore
instead of the native render
function that @testing-library
exposes, to render any component that is connected to Rematch store.
(For ex: using useSelector
, or useDispatch
)
Imagine an scenario where we have a component called <ButtonCounter>
that recovers information of the count model and increments the value:
tsx
importReact , {useEffect } from "react"import {useDispatch ,useSelector } from "react-redux"import type {RootState ,Dispatch } from "./store"Âexport constButtonCounter = () => {constdispatch =useDispatch <Dispatch >()constcounter =useSelector ((rootState :RootState ) =>rootState .count )Âreturn (<div ><span aria-label ="Counter">Current counter: {counter }</span ><button aria-label ="Increment Button"type ="button"onClick ={() =>dispatch .count .incrementAsync (1)}>Increment Asynchronous</button ></div >)}
tsx
importReact , {useEffect } from "react"import {useDispatch ,useSelector } from "react-redux"import type {RootState ,Dispatch } from "./store"Âexport constButtonCounter = () => {constdispatch =useDispatch <Dispatch >()constcounter =useSelector ((rootState :RootState ) =>rootState .count )Âreturn (<div ><span aria-label ="Counter">Current counter: {counter }</span ><button aria-label ="Increment Button"type ="button"onClick ={() =>dispatch .count .incrementAsync (1)}>Increment Asynchronous</button ></div >)}
We could test with Testing Library that pressing in that buttons correctly dispatches the incrementAsync effect and the counter
value it's correctly refreshed.
tsx
importReact from "react";import {screen } from "@testing-library/react";import {store } from "./store";import {renderWithRematchStore } from "./testUtils";Âimport {ButtonCounter } from "./ButtonCounter";Âdescribe ("ButtonCounter", () => {it ("should be rendered correctly", async () => {renderWithRematchStore (<ButtonCounter />,store );expect (screen .getByLabelText ("Increment Button")).toBeInTheDocument ();awaituserEvent .click (screen .getByLabelText ("Increment Button"));expect (screen .getByLabelText ("Increment Button")).toBeInTheDocument ();expect (screen .getByLabelText ("Counter")).toEqual ("Current counter: 1");});});
tsx
importReact from "react";import {screen } from "@testing-library/react";import {store } from "./store";import {renderWithRematchStore } from "./testUtils";Âimport {ButtonCounter } from "./ButtonCounter";Âdescribe ("ButtonCounter", () => {it ("should be rendered correctly", async () => {renderWithRematchStore (<ButtonCounter />,store );expect (screen .getByLabelText ("Increment Button")).toBeInTheDocument ();awaituserEvent .click (screen .getByLabelText ("Increment Button"));expect (screen .getByLabelText ("Increment Button")).toBeInTheDocument ();expect (screen .getByLabelText ("Counter")).toEqual ("Current counter: 1");});});
Thanks to aria-label
tags inside our ButtonCounter
component we can query this DOM elements with the screen
property exported by Testing Library.
We recover these DOM elements and we use the Testing Library assertions like .toBeInTheDocument()
to make sure that everything runs fine.