1

I'm testing the component Checkout which has two axios.get calls inside its useEffect, like so:

    useEffect(() => {
        let isMounted = true;

        if (isMounted) {
            axios
                .get("/cart/get-total") // 1st call
                .then((res) => {
                    setCartTotal(res.data)
                    );
                })
                .catch((err) => {
                    console.error(err);

            axios
                .get("/cart/get-subtotal") // 2nd call
                .then((res) => {
                    setUpfrontPayPalPayment(res.data); // 7% comission
                })
                .catch((err) => {
                    console.error(err);
                });
        }

        return () => (isMounted = false);
    }, []);

The first call is mocked using axiosMock from __mocks__/axios.js method:

axios.js

export default {
    post: jest.fn().mockResolvedValue({ data: {} }),
    get: jest.fn().mockResolvedValue({ data: {} }),
    // getAgain: jest.fn().mockResolvedValue({ data: {} }), // nope
};

However, I don't know how to mock the second call which is also a get call.

Checkout.test.js

jest.unmock("axios"); // unmock from previous test
import axiosMock from "../__mocks__/axios";

// faked props
const userInfo = {
    userName: "paco",
    userPhone: "1234567890",
};
const loggedIn = true;

describe("Checkout Component", () => {
    axiosMock.get.mockResolvedValueOnce({ data: 1600 });
    // axiosMock.get.mockResolvedValueOnce({ data: 120 }); // tried this for the second
    // axiosMock.getAgain.mockResolvedValueOnce({ data: 120 }); // also this


    it("renders", async () => {
        await act(async () => {
            render(
                <HashRouter>
                    <Checkout userInfo={userInfo} loggedIn={loggedIn} />
                </HashRouter>
            );
            screen.debug();
        });
    });
});

The Checkout.js component if you want to dive in deeper:

import axios from "axios";
import React, { useEffect, useState, useRef, useContext } from "react";

const Checkout = (props) => {
    // several useStates

    const context = useContext(Context);


    // this method gets fired onSubmit
    const handleTransferSubmit = () => {
        
        // two axios.post consecutive calls that should not affect at
        // all since they are fired onSubmit
    };

    // get user info and cart total-subtotal
    useEffect(() => {
        let isMounted = true;

        // if phone info available set it
        props.userInfo["userPhone"] &&
            setPhone(parseInt(props.userInfo["userPhone"]));

        if (isMounted) {
            axios
                .get("/cart/get-total")
                .then((res) => {
                    setCartTotal(
                        res.data
                    );
                })
                .catch((err) => {
                    console.error(err);
                    context.notifyToaster('error', 'Tenemos problemas con el servidor. Intenta más tarde');
                });

            axios
                .get("/cart/get-subtotal")
                .then((res) => {
                    setUpfrontPayPalPayment(res.data); // 7% comission
                })
                .catch((err) => {
                    console.error(err);
                    context.notifyToaster(
                        "error",
                        "Tenemos problemas con el servidor. Intenta más tarde"
                    );
                });
        }

        return () => (isMounted = false);
    }, []);

    // form validations
    useEffect(() => {
        let isMounted = true;

        return () => (isMounted = false);
    }, [
        orderName,
        phone,
        CP,
        streetName,
        addressNumber,
        deliveryDay,
        deliverySchedule,
        addressDetails,
        paymentMode,
    ]);

    return (
      <div className="container">
        {!loader ? (
          props.loggedIn && cartTotal > 1500 ? (
            <div style={{ marginBottom: "6rem" }}>
              <Form>
                {/* all the form info */}
                <Button
                  className="mb-5"
                  variant="primary"
                  type="submit"
                  disabled={buttonIsActive ? false : true}
                  onClick={handleTransferSubmit}
                >
                  Generar orden
                </Button>
              </Form>
            </div>
          ) : (
            <div>{/* if user is not logged in he can't see the form */}</div>
          )
        ) : (
          <CustomLoader />
        )}
      </div>
    );
};

export default withRouter(Checkout);

Can't get to see the updated component.

If I screen.debug() I notice that cartTotal which is set in the axios mocked response is not yet set when the render happens, causing that the condition for rendering the - Form - component cartTotal > 1500 is false.

Thanks again buddies!

Fer Toasted
  • 1,274
  • 1
  • 11
  • 27
  • 2
    It might make it easier to abstract your api calls into their own file, and then mock that file – Devon Norris Apr 22 '21 at 14:48
  • 2
    Your second call will be handled by the same mock function, you can use e.g. `NthCalledWith` to check that. If you're looking to return _different_ values, `mockResolvedValueOnce` or even `mockImplementation` can help. Extracting services that act as a facade over Axios, as suggested above, would simplify the component and make it easier to test, though. – jonrsharpe Apr 22 '21 at 16:18

1 Answers1

1

As per @jonrsharpe comments and Jest docs - https://jestjs.io/docs/mock-function-api#mockfnmockresolvedvalueoncevalue - I could mock two consecutive axios calls by chaining mockResolvedValueOnce. I was using them sepparated at the beginning.

Before

axiosMock.get.mockResolvedValueOnce({ data: 1600 });
axiosMock.get.mockResolvedValueOnce({ data: 120});

After

axiosMock.get
    .mockResolvedValueOnce({ data: 1600 })
    .mockResolvedValueOnce({ data: 120 })
Fer Toasted
  • 1,274
  • 1
  • 11
  • 27
  • This idea also works with vitest. This fixes my problem with two axios calls from my component (1st call is triggered directly in my component via function, 2nd is triggered from custom react hook). `axios.get = vi.fn().mockResolvedValueOnce(mockTimeline).mockResolvedValueOnce(mockStatistic)` – josevoid Aug 04 '22 at 07:11