Marco
Marco

Reputation: 644

State no updating in class component when testing method with jest

I am trying to test my method in a context provider. I have one branch in the method to be covered and that's what I am strangling with. The specific branch is only entered when a specific condition occurs: if (offset !== 0 && total !== 0 && offset >= total)

See my Class component below:

class JourneyProvider extends Component<
  {
    children: ReactNode;
  },
  JourneyContextData
> {
  constructor(props: { children: ReactNode }) {
    super(props);
    this.state = {
      ...defaultValues,
    };
  }

  getContextValue = (): JourneyContextData => {
    const { products, total, limit, offset, loading, disabled, setProducts } =
      this.state;
    return {
      products,
      total,
      limit,
      offset,
      loading,
      disabled,
      setProducts,
    };
  };

  setProducts = async (): Promise<void> => {
    const { limit, offset, total, products } = this.state;
    if (total === 0 || offset < total) {
      const gqlRequest = new GQLRequest(query);
      this.setLoading(true);
      try {
        await gqlRequest.post().then(({ products: { edges, totalCount } }) => {
          const newOffset = offset + limit;
          this.setState({
            products,
            total: totalCount,
            offset: newOffset,
          });
          this.setLoading(false);
          // Disable button if there are no more products
          if (offset !== 0 && total !== 0 && offset >= total) {
            // never gets in here when testing.
            this.setDisabled(true);
          }
        });
      } catch (e) {
        this.setLoading(false);
      }
    }
  };
}

This is my test:

  it("setProducts is successful and disable button", async () => {
    const wrapper = shallow(
      <JourneyProvider>
        <div>test</div>
      </JourneyProvider>
    ) as any;

    const result = {
      products: {
        edges: [
          {
            node: {
              id: "1",
              name: "test-name",
            },
          },
        ],
        totalCount: 1,
      },
    };

    mockedClient.post.mockResolvedValueOnce(result);

    jest
      .spyOn(ProductsQuery, "getProductsQuery")
      .mockResolvedValueOnce(new Query("test", true) as never);

    const setLoadingSpy = jest.spyOn(wrapper.instance(), "setLoading");
    const setDisabledSpy = jest.spyOn(wrapper.instance(), "setDisabled");

    wrapper.state().limit = result.products.totalCount;
    console.log(wrapper.state().offset); //this returns 0
    await wrapper.instance().setProducts();
    console.log(wrapper.state().offset); //this returns 0
    expect(setLoadingSpy).toHaveBeenCalledWith(true); // this passes

    expect(setLoadingSpy).toHaveBeenCalledWith(false); // this passes

    expect(setDisabledSpy).toHaveBeenCalledWith(true); // this fails
  });

Upvotes: 0

Views: 332

Answers (1)

diedu
diedu

Reputation: 20775

You should change the logic in the code where you're making the comparison because you're using a stale state, keep in mind that a call to setState doesn't change the state immediately. Compare the new values you set to the state instead of the old state values

          if (newOffset !== 0 && totalCount !== 0 && newOffset >= totalCount) {

or put that code inside the setState callback to guarantee you're using updated values

...
          const newOffset = offset + limit;
          this.setState({
            products,
            total: totalCount,
            offset: newOffset,
          }, () => {
            this.setLoading(false);
            if (offset !== 0 && total !== 0 && offset >= total) {
              this.setDisabled(true);
            }
          });
...

Upvotes: 1

Related Questions