Testing Redux and Components

Testing Redux and Components

Little briefing on my recent experience

Just got done doing some testing and I want to write about the important aspects of it.

ucabGoSlice.js

The ucabGoSlice handles all the functions that update the state of the ucabGo store. Functions like onSetActiveProduct , onUpdateProduct , etc. It also holds the state of the store like stores, orders, clients, etc.

To test this, you can create a folder of fixtures in the test suite.

As you can see, you can create 'test subjects' or some states than can be used to test your slice.

Since I am lazy and I don't feel like explaining all the code, I'll just show the tests below.

import { onAddNewProduct, onDeleteProduct, onLoadProducts, onSetActiveProduct, onUpdateProduct, ucabGoSlice } from "../../../src/store/ucabGo/ucabGoSlice"
import { appWithActiveProductState, appWithProductsState, initialState, products } from "../../__fixtures__/ucabGoStates";

describe('tests in ucabGoSlice', () => { 
    test('should return default state', () => { 
        expect(ucabGoSlice.getInitialState()).toEqual(initialState)
    });

    test('onSetActiveProduct should activate a product', () => { 
        const state = ucabGoSlice.reducer(appWithProductsState, onSetActiveProduct(products[0]))
        expect(state).toEqual(appWithActiveProductState);
    });

    test('should add a new product to the state', () => { 
        const newProduct = {
            id: '4',
            name: 'Arroz con Pollo',
            desc: 'Hecho en casa',
            img: 'http://res.clouiwerwesdfojoidf',
            active: true,
        }
        const state = ucabGoSlice.reducer(appWithProductsState, onAddNewProduct(newProduct))
        expect(state.products).toEqual([...products, newProduct]);
    })

    test('should update the product', () => { 
        const updatedProduct = {
            id: '1',
            name: 'Arroz con Pollo',
            desc: 'Hecho en casa',
            img: 'http://res.clouiwerwesdfojoidf',
            active: true,
        }
        const state = ucabGoSlice.reducer(appWithProductsState, onUpdateProduct(updatedProduct));
        expect(state.products).toContain(updatedProduct);
    })

    test('should delete a product', () => { 
        const state = ucabGoSlice.reducer(appWithActiveProductState, onDeleteProduct());
        expect(state.products).not.toContain(products[0])
        expect(state.activeProduct).toBe(null)
    })

    test('should load products', () => { 
        const state = ucabGoSlice.reducer(initialState, onLoadProducts(products));
        expect(state.isLoadingProducts).toBeFalsy();
        expect(state.products).toEqual(products)
    })

 })

FabAddNew

I also started a test in a react Component, where I used the React Testing Library.

The test is very minimal but it shows something important.

    render(
      <Provider store={store}>
        <FabAddNew />
      </Provider>
    );
    expect(fireEvent.click(screen.getByRole("button"))).toBeTruthy();

This is the component I am testing:

export const FabAddNew = () => {
  const { openProductModal } = useUiStore();

  const handleClickNew = () => {
    openProductModal();
  };

  return (
    <button
      className="btn btn-success fab"
      onClick={handleClickNew}
      style={{ boxShadow: "rgba(149, 157, 165, 0.2) 0px 8px 24px" }}
    >
      <i className="fas fa-plus"></i>
    </button>
  );
};

The render method allows us to simulate the rendering of a React component. But the FabAddNew is a component that affects the store. So we wrap it on <Provider> tags.
Then the fireEvent.click(screen.getByRole("button")) simulates the click of an element.

Sadly, I wanted to test that handleClickNew was being executed when the component is clicked, but I don't know how at the moment. 😢

UseUiStore

Now here it gets weird. UseUiStore holds 2 functions for handling the isProductModalOpen state. And we want to test that these functions work.
First off, we must create a 'mockStore' that kinda simulates our store.

const getMockStore = ( initialState ) => {
    return configureStore({
        reducer: {
            ui: uiSlice.reducer
        },
        preloadedState: {
            ui: {...initialState}
        }
    })
}

To make sure that we are getting the default state and it is what we expect:

        const mockStore = getMockStore({ isProductModalOpen: false });
        const { result } = renderHook( () => useUiStore(), {
            wrapper: ({children}) => 
                <Provider store={ mockStore }>
                    {children}
                </Provider>
        });

        expect(result.current).toEqual({
            isProductModalOpen: false,
            openProductModal: expect.any(Function),
            closeProductModal: expect.any(Function),
        });

As you can see, we must wrap this in a wrapper because it affects the store.

Now to check if the states are being changed correctly:

const { openProductModal } = result.current;

act( () => {
     openProductModal()
});

We copy the mockStore and {result} lines of the first test and use act() to update the state.

🥶🥶🥶🥶🥶🥶🥶