0

To start I'm conditionally rendering a component that's reliant on the useState hook being set as a result of useEffect hook. Here's a code sample:

function Component() {
  const [response, setResponse] = useState();
  const [fail, setFail] = useState();
  /**
   * @function getModels - fetch data to populate the models
   * */
  const fetchStuff = async () => {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then((data) => {
        const storage = [data];
        setResponse(storage);
        setFail(false);
      })
      .catch((err) => {
        setResponse(err);
        setFail(true);
      });
  };

  useEffect(() => {
    fetchStuff();
  }, []);

  if (fail === true) {
    return (
      <p>
        ERROR:
        {fail}
      </p>
    );
  }
  if (fail === false) {
    return (
      <p>
        Success:
        {response}
      </p>
    );
  }
  return <p>Loading Screen</p>;
}

My current point of contention is that I'm unable to call setResponse or setFail and update the state of fail or response. I believe that I need to use mount as opposed to shallow rendering? Also, I understand that testing philosophies would argue against conducting a test in this fashion. However, I am seeking an a solution that enables updating the state. Any advice would be greatly appreciated.

Lin Du
  • 88,126
  • 95
  • 281
  • 483
N8BIZ
  • 151
  • 1
  • 14
  • So, I found a solution here: https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga But this updates the state of both useState hooks. Is there a means of setting each useState hook independently? – N8BIZ Dec 10 '20 at 23:32
  • https://stackoverflow.com/questions/57025753/how-to-set-initial-state-for-usestate-hook-in-jest-and-enzyme The comment from Jimmy links to mockImplementationOnce – N8BIZ Dec 11 '20 at 02:12

1 Answers1

1

You can mock fetch API call to and its resolved value. Then, you can assert what exactly the component renders. We should use whenStable function to make sure the mocked API calls are completed.

Component.jsx:

import React, { useState, useEffect } from 'react';

export function Component() {
  const [response, setResponse] = useState();
  const [fail, setFail] = useState();
  /**
   * @function getModels - fetch data to populate the models
   * */
  const fetchStuff = async () => {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then((data) => {
        const storage = [data];
        setResponse(storage);
        setFail(false);
      })
      .catch((err) => {
        setResponse(err);
        setFail(true);
      });
  };

  useEffect(() => {
    fetchStuff();
  }, []);

  if (fail === true) {
    return (
      <p>
        ERROR:
        {fail}
      </p>
    );
  }
  if (fail === false) {
    return (
      <p>
        Success:
        {response}
      </p>
    );
  }
  return <p>Loading Screen</p>;
}

Component.test.jsx:

import React from 'react';
import { Component } from './Component';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';

const whenStable = async () => {
  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });
};

describe('65243384', () => {
  let fetch;
  beforeEach(() => {
    fetch = global.fetch;
  });
  afterEach(() => {
    global.fetch = fetch;
  });
  it('should success', async () => {
    global.fetch = jest.fn().mockResolvedValueOnce('mocked success data');
    const wrapper = mount(<Component></Component>);
    expect(wrapper.find('p').text()).toBe('Loading Screen');
    await whenStable();
    expect(wrapper.find('p').text()).toBe('Success:mocked success data');
    expect(global.fetch).toBeCalledWith('https://jsonplaceholder.typicode.com/todos/1');
  });

  it('should fail', async () => {
    const mErr = new Error('network');
    global.fetch = jest.fn().mockRejectedValueOnce(mErr);
    const wrapper = mount(<Component></Component>);
    expect(wrapper.find('p').text()).toBe('Loading Screen');
    await whenStable();
    expect(wrapper.find('p').text()).toBe('ERROR:');
    expect(global.fetch).toBeCalledWith('https://jsonplaceholder.typicode.com/todos/1');
  });
});

unit test result:

 PASS  examples/65243384/Component.test.jsx
  65243384
    ✓ should success (44 ms)
    ✓ should fail (5 ms)

---------------|---------|----------|---------|---------|-------------------
File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------|---------|----------|---------|---------|-------------------
All files      |     100 |      100 |     100 |     100 |                   
 Component.jsx |     100 |      100 |     100 |     100 |                   
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.121 s

source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/65243384

Lin Du
  • 88,126
  • 95
  • 281
  • 483