Search code examples
reactjsreduxreact-testing-libraryredux-mock-store

Testing a Redux Change with Testing Library


I am having difficulty testing my redux application. I am not sure if there is some kind of asynch issue, or there is something I dont quite understand about React Testing Library. Needless to say I am pretty lost with RTL. With Enzyme I had no problems with this kind of stuff.

I want to test a component which is basically a 'product' which has some text with a combo box for 'quantity'.

The component is pretty straightforward and looks like this :

function ItemBox(props: Props) {

    const dispatch = useDispatch();

    let selected = ""

    if (props.product.amount && props.product.amount > 0) {
        selected = "selected"
    }

    function handleChangeQuantity(e: any) {
        dispatch(createUpdateProductSelection({value: e.currentTarget.value, productid: props.product.id}));
    }

    return(
        <div className={"item-box " + selected}>
            <div className={"item-box-desc " + selected} title={props.product.name} >
                {props.product.name}
            </div>
            <div className={"item-box-bottom"} >
                <select className={"item-box-options"} id="qty" onChange={handleChangeQuantity} value={props.product.amount}>
                    <option value="0">0</option>
                    <option value="1">1</option>
                    <option value="2">2</option>
                    <option value="3">3</option>
                </select>
            </div>
        </div>
    )
}

In my reducer I catch the action and update the product.

export function productsReducer(state: ProductsState, action: AnyAction): ProductsState {

    if (createUpdateProductSelection.match(action)) {

        let allProducts = state.allProducts.map((item): Product => {
            if (item.id === action.payload.productid) {
                return {
                    ...item,
                    amount: action.payload.value
                }
            }
            return item;
        })

        return {
            ...state,
            allProducts: allProducts,
        };
    }
    return state;
}

In the actual application this all works fine.

When I get to writing the test things get weird. My test looks like this :

let comboboxes: HTMLInputElement[] = await screen.findAllByRole("combobox");

fireEvent.change(comboboxes[0], {target: {value: "3"}})

comboboxes = await screen.findAllByRole("combobox");

expect(comboboxes[0].value).toEqual("3") 

console.info("" + screen.debug(comboboxes[0]))

The test passes because it finds that the combobox has been set to '3'. However when I look at the HTML in the console.info I see a "select" but there is no option that is set to selected (ie. <option value="3" selected> ).

It looks like this :

<select class="item-box-options" id="qty">
    <option value="0">0</option>
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
    <option value="4">4</option>
</select>

Maybe this is an issue with this component being a controlled component?

In any case debugging through the test shows me that it is breaking correctly at the callback handleChangeQuantity . But it is NOT breaking at the reducer level.

Before it gets to the reducer level it breaks at this line in my test :

comboboxes = await screen.findAllByRole("combobox");

Which basically means the react behaviour has not completed before it has gotten to this test line.

Am I 'waiting' correctly?

Is something async happening in the background that I am not aware of? This should all be 'sync' because it is a trivial redux scenario as far as I am concerned.


Solution

  • Silly me,

    Seems like I was using redux-mock-store which was the problem

    By using mockstore like this, I get no "reducer" action happening.

    const mockStore = configureStore();
    
    let store = mockStore(getData());
    

    If I create the ACTUAL store that I used in my application in the "before" methods of my tests then my tests work, because they are also getting the reducers :

    const store = createStore(
        combineReducers({
            // login: login,
            products: products,
            order: order,
            admin: admin
        }), getData()
    );
    return store;
    

    In general mock-store shouldnt be used