2
votes

I am using new Redux toolkit. New Redux-toolkit much efficient than old redux. I have create multiple reducers and actions by using new Redux-toolkit. I destructor the redux-toolkit setup little bit. I am using Enzyme and Jest for unit test. My redux counter intialState is 1. From my testing, inside it scope I first take the intialState then after simulate('click') increase button, I got result 2, which I expected. When I try to test my decrease button inside the it scope it takes the result from increase's it scope. If I put intialState 1 inside the decrease button's it scope, it gives me failed test because it expected 2. I think I need to create mockStore for this counter.test. Since the redux-toolkit's syntax are new to me, I don't how to create the mockstore inside counter.test test suite.

Ps. There is also other react component where it fetch data, todolist. I also want to do unit test those components. if anyone help me to test those components I will be really glad. I am newbie in unit testing ????.

I uploaded my code to Codesandbox.

Below I am explaining how i did the setup and passing to React's component.

This is counter reducer

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  "name": `counter`,
  "initialState": 1 as number,
  "reducers": {
    "increment": (state) => state + 1,
    "decrement": (state) => state - 1
  }
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;

This is combineReducers

import { combineReducers } from 'redux';
import counter from 'store/reducer/counter';
import todo from 'store/reducer/todo/todo';
import fetchUser from 'store/reducer/fetch';
import fetching from 'store/reducer/createAsyncAxios';
const rootReducer = combineReducers({
  counter, //This is my counter Reducer
  todo,
  fetchUser,
  fetching
});

export type IRootState = ReturnType<typeof rootReducer>;
export default rootReducer;

This is my store file

import { configureStore, Action, getDefaultMiddleware } from '@reduxjs/toolkit';
import { ThunkAction } from "redux-thunk";

import rootReducer, { IRootState } from 'store/combineReducer';


const store = configureStore({
  "reducer": rootReducer,
  "middleware": [...getDefaultMiddleware()]
  
});

export type AppThunk = ThunkAction<void, IRootState, null, Action<string>>
export default store;

Ps. I destructor the rootfile. first I created a root file where I imported store then I connected to App. I did it because I can direct import rootfile to test suite

import React from 'react';
import { Provider } from 'react-redux';
import store from 'store/store';
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    background-color: #282c34;
    color: white;
    font-family: "Lucida Console", Courier, monospace;
  }
`;

interface IProps {
  children: JSX.Element[] | JSX.Element;

}
export default ({ children }: IProps) => {
  return (
    <Provider store={store}>
      <GlobalStyle />
      {children}
    </Provider>
  );
};

This is how connected my rootfile to App

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import Root from './root';

ReactDOM.render(
  <Root>
    <App />
  </Root>
  ,
  document.getElementById(`root`)
);

This is my counter component

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from 'store/reducer/counter/index';
import { IRootState } from 'store/combineReducer';
import styled from 'styled-components';
const Button = styled.button`
background-color: #4CAF50; /* Green */
  border: none;
  color: white;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
`;

const Text = styled.h1`
color: blue;
`;

export default () => {
  const counter = useSelector((state: IRootState) => state.counter);

  const dispatch = useDispatch();
  return (
    <div >
      <Text>{counter}</Text>
      <Button data-test="increment" onClick={() => dispatch(increment())}>
        Increment counter
      </Button>
      <br></br>
      <br></br>
      <Button data-test="decrement" onClick={() => dispatch(decrement())}>
        Decrement counter
      </Button>
    </div>
  );
};

This is my counter test suite

import React from 'react';
import { mount } from "enzyme"; // mount is full dom renderning function with children
import Counter from 'components/counter';
import Root from "root/index";

let wrapped;
beforeEach(() => {
  // I need to create mock store in here. I don't know how to do that.
  wrapped = mount(
    <Root >
      <Counter />
    </Root>
  );
  // console.log(wrapped.debug());
});

afterEach(() => {
  wrapped.unmount(); // it cleans the mount after test.
});


describe(`This is counter component`, () => {
  it(``, () => {
    expect(wrapped.find(`h1`).text()).toEqual(`1`);
  });

  it(`after click it will increase the value`, () => {
    expect(wrapped.find(`h1`).text()).toEqual(`1`);
    wrapped.find(`button`).at(0).find(`[data-test="increment"]`).simulate(`click`);
    expect(wrapped.find(`h1`).text()).toEqual(`2`);
  });
  it(`after click it will decrease the value`, () => {
    expect(wrapped.find(`h1`).text()).toEqual(`1`); // Test failed: because it Received: "2"
    wrapped.find(`button`).at(1).find(`[data-test="decrement"]`).simulate(`click`);
    expect(wrapped.find(`h1`).text()).toEqual(`2`); //
  });
});
1
How about redux-mock-store?k-wasilewski
I can try :)...Krisna
@k-wasilewski did not succeed. feels like dumb.Krisna
I've added an answer to clarify..k-wasilewski

1 Answers

0
votes

I would try redux-mock-store if I were you, you can reset your store after each test (or before each test, for that matter).

This is an example from the official documentation:

// src/actions/users.test.js 
import mockAxios from "axios";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import promiseMiddleware from "redux-promise-middleware";
import { getUsers } from "./users";

const mockStore = configureMockStore([thunk, promiseMiddleware()]);

describe("User Actions", () => {
  let store;

  beforeEach(() => {
    store = mockStore({
      users: {}
    });
  });

Inside the beforeEach function we reset the value of our store, so we don’t have any unexpected results in our assertions.

You would have to pass this mocked store to your app, and the easiest way to do it would be to move store Provider one level up in your app (to index.js). Then, you'd mock it like this:

wrapped = mount(
    <Provider store={mockStore}>
        <Root>
            <Counter />
        </Root>
    </Provider>  
);