1

I'm beginner with react and redux and I coded an very simple app to learn it where you can add expenses with a description an amount a date and a note.

I'm testing one of my reducers with jest and I get an error that I cannot understand. I'm probably missing something but I can't figure out so it would be nice if you could help me out.

So I have one expenseSlice with a reducer action addExpense that prepare the payload just adding an ID and set default values if necessary and add it to the previous state.

import { createSlice } from "@reduxjs/toolkit"
import { nanoid } from "nanoid"

const expensesDefaultState = []

const expensesSlice = createSlice({
    name: 'expenses',
    initialState: expensesDefaultState,
    reducers: {
        addExpense: {
            reducer: (state, action) => {
                return [
                    ...state,
                    action.payload
                ]
            },
            prepare: (payload) => {
                return {
                    payload: {
                        id: nanoid(),
                        description: payload.description ? payload.description : '',
                        note: payload.note ? payload.note : '',
                        amount: payload.amount ? payload.amount : 0,
                        createdAt: payload.createdAt ? payload.createdAt : 0
                    }
                }
            }
        }

And in my test file when I test the action the test pass correctly like so :

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

    it('should returns an addExpense action object with prepared payload', () => {
        const newExpense = {
            description: 'this is a description',
            amount: 1000,
            createdAt: 0,
            note: 'this is anote'
        }
        expect(addExpense(newExpense)).toEqual({
            type: 'expenses/addExpense',
            payload: {
                id: expect.any(String),
                description: 'this is a description',
                amount: 1000,
                createdAt: 0,
                note: 'this is a note'
            }
        })
    })

But when I test the reducer with the same action and expect an id it failed and I get the object without the ID like so :

describe('Expense Reducer', () => {

    it('should add an expense', () => {
        const newExpense = {
            description: 'my new expense',
            amount: 1000,
            createdAt: 0,
            note: 'this is a note'
        }
        const state = expenseReducer([], {
            type: 'expenses/addExpense',
            payload: newExpense
        })

        expect(state).toEqual([
            {
                id: expect.any(String),
                description: 'my new expense',
                amount: 1000,
                createdAt: 0,
                note: 'this is a note'
            }
        ])
    })
})

And the error :

● Expense Reducer › should add an expense

    expect(received).toEqual(expected) // deep equality

    - Expected  - 1
    + Received  + 0

      Array [
        Object {
          "amount": 1000,
          "createdAt": 0,
          "description": "my new expense",
    -     "id": Any<String>,
          "note": "this is a note",
        },
      ]

What I'm missing here or not understanding correctly ? Thank you in advance !

1 Answer 1

1

Because you ran the expensesSlice.reducer function directly, it's a pure function. So the prepare callback will not execute. You can use store.dispatch(addExpense(newExpense)) to trigger the prepare callback and test the changes of the state slice.

createSlice() will use createAction() to create action creators with or without prepare callback and export these action creators as stateSlice.actions property. See v1.7.2/packages/toolkit/src/createSlice.ts#L294

When the action creators execute like addExpense(newExpense), it will use prepare callback to customize action content(the id: nanoid() in your case).

E.g.

index.ts:

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

const expensesDefaultState = [];

const expensesSlice = createSlice({
  name: 'expenses',
  initialState: expensesDefaultState,
  reducers: {
    addExpense: {
      reducer: (state, action) => {
        return [...state, action.payload];
      },
      prepare: (payload) => {
        return {
          payload: {
            id: nanoid(),
            description: payload.description ? payload.description : '',
            note: payload.note ? payload.note : '',
            amount: payload.amount ? payload.amount : 0,
            createdAt: payload.createdAt ? payload.createdAt : 0,
          },
        }
      },
    },
  },
});

export const { addExpense } = expensesSlice.actions;
export default expensesSlice.reducer;

index.test.ts:

import { configureStore, nanoid } from '@reduxjs/toolkit';
import expenseReducer, { addExpense } from '.';

describe('Expenses actions', () => {
  it('should returns an addExpense action object with prepared payload', () => {
    const newExpense = {
      description: 'this is a description',
      amount: 1000,
      createdAt: 0,
      note: 'this is a note',
    };
    expect(addExpense(newExpense)).toEqual({
      type: 'expenses/addExpense',
      payload: {
        id: expect.any(String),
        description: 'this is a description',
        amount: 1000,
        createdAt: 0,
        note: 'this is a note',
      },
    });
  });

  it('should add an expense', () => {
    const newExpense = {
      id: nanoid(),
      description: 'my new expense',
      amount: 1000,
      createdAt: 0,
      note: 'this is a note',
    };
    const state = expenseReducer([], {
      type: 'expenses/addExpense',
      payload: newExpense,
    });

    expect(state).toEqual([
      {
        id: expect.any(String),
        description: 'my new expense',
        amount: 1000,
        createdAt: 0,
        note: 'this is a note',
      },
    ]);
  });

  it('should add an expense - 2', () => {
    const newExpense = {
      description: 'my new expense',
      amount: 1000,
      createdAt: 0,
      note: 'this is a note',
    };
    const store = configureStore({ reducer: expenseReducer });
    store.dispatch(addExpense(newExpense));
    expect(store.getState()).toEqual([
      {
        id: expect.any(String),
        description: 'my new expense',
        amount: 1000,
        createdAt: 0,
        note: 'this is a note',
      },
    ]);
  });
});

Test result:

 PASS  stackoverflow/72677850/index.test.ts (10.106 s)
  Expenses actions
    ✓ should returns an addExpense action object with prepared payload (2 ms)
    ✓ should add an expense (1 ms)
    ✓ should add an expense - 2 (2 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        10.577 s, estimated 13 s
Sign up to request clarification or add additional context in comments.

2 Comments

Oh thank you ! I got it now! I wonder if I need to test the dispatch since I know that the prepare function work well with the previous test tho
In my case changes are not shown in dom

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.