1

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,
    });
};
Matthew Francis
  • 670
  • 3
  • 10
  • 26
  • Can you provide the actual error instead of a screenshot? But it looks like origin being passed to Nock is incorrect. – Matt R. Wilson Aug 21 '20 at 10:48
  • Apologies, I used a fake one for privacy reasons. But I've updated the code and replaced the origin with my actual database site. I have also received a new error and included it in the post. – Matthew Francis Aug 21 '20 at 19:06

1 Answers1

1

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();
});
Matthew Francis
  • 670
  • 3
  • 10
  • 26