Search code examples
reactjsreduxjestjsenzymenock

Jest/Enzyme - How to use nock library with a connected redux store component?


Goal

We want to test the async call, fetchReviews() on componentDidMount() in Reviews component, and check if it updates our Review component afterwards.

Here's Review:

const Review: React.FC<ReviewProps> = (props) => {
    useEffect(() => {
        props.fetchReviews();
    }, []);

//other code...

const mapStateToProps = (state: StoreState): FetchReviewsResponse => {
    return {
        reviews: state.reviews,
    };
};
export default connect(mapStateToProps, { fetchReviews })(Review);

Problem

The testing terminal gives me an error of:

Given action "1", reducer "reviews" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined.

 return dispatch<FetchReviewsAction>({
         |            ^
      28 |         //Generic is an extra step to ensure that everything has the right values
      29 |         type: ActionTypes.FETCH_REVIEWS,
      30 |         payload: response.data,

To demonstrate, here's my Review.test:

import React from "react";
import nock from "nock";
import { mount, ReactWrapper } from "enzyme";
import Root from "Root";
import Body from "components/Body";
import waitForExpect from "wait-for-expect";
import ReviewBox from "components/ReviewBox";


describe("<Body> integration", () => {
    let wrapper: ReactWrapper;
    let mockData: any;
    beforeEach(() => {
        mockData = [
            {
                id: 1,
                username: "Big Fish",
                date: "2017-09-16",
                title: "Apple Store Review",
                description: "App Description 1",
                rating: 1,
            },
        ];

        wrapper = mount(
            <Root>
                <Body />
            </Root>
        );
    });

    it("has the correct type and payload", async () => {
        //mock fetchReviews()

        const scope = nock("https://apple-review-backend.vercel.app")
            .get("/db.json")
            .query(true)
            .reply(200, mockData, { "Access-Control-Allow-Origin": "*" });

        await waitForExpect(() => {
            wrapper.update();
            expect(scope.isDone()).toBe(true);
            expect(wrapper.find(ReviewBox).length).toEqual(1);
        });
    });
});

Review Reducer

export default (state: Review[] = [], action: FetchReviewsAction) => {
    switch (action.type) {
        case ActionTypes.FETCH_REVIEWS:
            return action.payload.reviews;
        default:
            return state;
    }
};


Reviews Action


export const fetchReviews = () => async (dispatch: Dispatch) => {
    const response = await reviews.get<FetchReviewsResponse>("/db.json");
    return dispatch<FetchReviewsAction>({
        //Generic is an extra step to ensure that everything has the right values
        type: ActionTypes.FETCH_REVIEWS,
        payload: response.data,
    });
};

Solution

  • Solved it! The issue was not including

    act(async () => {
        //https://github.com/enzymejs/enzyme/issues/2423
        //https://stackoverflow.com/questions/55047535/testing-react-components-that-fetches-data-using-hooks
        wrapper = mount(
            <Root>
                <Body />
            </Root>
        );
    });
    

    It seems that fetchReviews(..) (which uses axios) was resolved after our test ends. By covering it with act(async()...) we can "wait" fetchRequest() to finish so that we can mock it with nock.

    Full code:

    describe("<Body> integration", () => {
        let wrapper: ReactWrapper;
        let mockData: any;
    
        beforeEach(async () => {
            mockData = {
                reviews: [
                    {
                        id: 1,
                        username: "Big Fish",
                        date: "2017-09-16",
                        title: "Apple Store Review",
                        description: "App Description 1",
                        rating: 1,
                    },
                    {
                        id: 2,
                        username: "ILoveApple",
                        date: "2017-10-16",
                        title: "Review of App",
                        description: "App Description 2",
                        rating: 2,
                    },
                ],
            };
    
            await act(async () => {
                //https://github.com/enzymejs/enzyme/issues/2423
                //https://stackoverflow.com/questions/55047535/testing-react-components-that-fetches-data-using-hooks
                wrapper = mount(
                    <Root>
                        <Body />
                    </Root>
                );
            });
        });
    
        it("has the correct type and payload", async () => {
            const scope = nock("https://apple-review-backend.vercel.app")
                .get("/db.json")
                .reply(200, mockData, { "Access-Control-Allow-Origin": "*" });
    
            await waitForExpect(() => {
                wrapper.update();
                expect(scope.isDone()).toBe(true);
                expect(wrapper.find(ReviewBox).length).toEqual(2);
            });
        }, 30000);
    });
    
    afterEach(function () {
        // if (!nock.isDone()) {
        //     console.log("Not all nock interceptors were used!");
        //     nock.cleanAll();
        // }
        nock.restore();
    });