Oliver Watkins
Oliver Watkins

Reputation: 13489

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.

Upvotes: 0

Views: 2191

Answers (2)

Lin Du
Lin Du

Reputation: 102207

There is a note in the documentation of redux-mock-store

Please note that this library is designed to test the action-related logic, not the reducer-related one. In other words, it does not update the Redux store.

Take a look dispatch() method, it just store the redux action in the actions array. It doesn't execute any reducers. it even doesn't accept any of them during setup. It only notifies the list of subscribers. So the state will be always the same.

To test the reducer and its states you don't need any kind of mock, just use the createStore from redux. So that the state will be updated and the useSelector() hook will be executed again.

Upvotes: 1

Oliver Watkins
Oliver Watkins

Reputation: 13489

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

Upvotes: 1

Related Questions