admin管理员组

文章数量:1394099

While learning React, I'm trying to understand custom hooks.

I've created a simple one updating an internal "step" on creation, cleanup, and allows for external updates.

But I'm struggling to successfully implements its unit tests on "step" update and cleanup. It's as if the the returned "step" value is not updated anymore after the hook creation.

How can fix the tests? Did I miss some key concepts?

Here is the custom hook:

const useCustomHook = (stepToSet: string) => {
  const [step, setStep, ] = useState('');

  useEffect(() => {
    if (stepToSet) {
      setStep(stepToSet);
    }

    return () => {
      setStep('cleanup');
    };
  }, [stepToSet, ]);

  const externalCb = (externalStepToSet: string) => {
    setStep(externalStepToSet);
  };

  return {
    step,
    externalCb,
  };
};

Jest unit test:

import {
  act, renderHook, 
} from '@testing-library/react-hooks';
import { expect, } from 'chai';

describe('useCustomHook Hook', () => {

  it('step updates test', async () => {

    const {
      result,
      unmount,
      waitFor,
    } = renderHook(() => {
      return useCustomHook('created');
    });

    const {
      step,
      externalCb,
    } = result.current;

    expect(step).to.be.equal('created');

    act(() => externalCb('updated'));
    await waitFor(() => {});
    expect(step).to.be.equal('updated');

    act(() => unmount());
    await waitFor(() => {});
    expect(step).to.be.equal('cleaned');
  });
});

The test keeps failing with the message:

assert.strictEqual(received, expected)

Expected value to strictly be equal to:
  "updated"
Received:
  "created"

Message:
  expected 'created' to equal 'updated'

Difference:

- Expected
+ Received

- updated
+ created
...

The test still fails with the same message if I remove async and the await lines.

While learning React, I'm trying to understand custom hooks.

I've created a simple one updating an internal "step" on creation, cleanup, and allows for external updates.

But I'm struggling to successfully implements its unit tests on "step" update and cleanup. It's as if the the returned "step" value is not updated anymore after the hook creation.

How can fix the tests? Did I miss some key concepts?

Here is the custom hook:

const useCustomHook = (stepToSet: string) => {
  const [step, setStep, ] = useState('');

  useEffect(() => {
    if (stepToSet) {
      setStep(stepToSet);
    }

    return () => {
      setStep('cleanup');
    };
  }, [stepToSet, ]);

  const externalCb = (externalStepToSet: string) => {
    setStep(externalStepToSet);
  };

  return {
    step,
    externalCb,
  };
};

Jest unit test:

import {
  act, renderHook, 
} from '@testing-library/react-hooks';
import { expect, } from 'chai';

describe('useCustomHook Hook', () => {

  it('step updates test', async () => {

    const {
      result,
      unmount,
      waitFor,
    } = renderHook(() => {
      return useCustomHook('created');
    });

    const {
      step,
      externalCb,
    } = result.current;

    expect(step).to.be.equal('created');

    act(() => externalCb('updated'));
    await waitFor(() => {});
    expect(step).to.be.equal('updated');

    act(() => unmount());
    await waitFor(() => {});
    expect(step).to.be.equal('cleaned');
  });
});

The test keeps failing with the message:

assert.strictEqual(received, expected)

Expected value to strictly be equal to:
  "updated"
Received:
  "created"

Message:
  expected 'created' to equal 'updated'

Difference:

- Expected
+ Received

- updated
+ created
...

The test still fails with the same message if I remove async and the await lines.

Share Improve this question edited Mar 13 at 19:54 Drew Reese 204k18 gold badges245 silver badges273 bronze badges asked Mar 13 at 17:09 DgurioDgurio 132 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

Issues

  • The result is holds a reference to the hook's current return value, but in your test you only access the initial value before effecting any changes, the later assertions don't have the updated hook value.
  • When a hook/component unmounts, any enqueued state updates are a non-operation. After unmounting no state updates will be processed.

Solution Suggestions

  • Update the test cases to access result.current each time after an act.
  • Remove any assertions after unmounting the hook.
  • The waitFor calls appear to be superfluous since they are empty and could/should probably be removed. AFAIK act is sufficient for triggering a re-render and hook update.
import { act, renderHook } from "@testing-library/react";
import { expect } from "chai";
import { useCustomHook } from "./useCustomHook";

describe("useCustomHook Hook", () => {
  it("step updates test", () => {
    const { result, unmount } = renderHook(useCustomHook, {
      initialProps: "created",
    });

    // Initial step value when mounted
    expect(result.current.step).to.be.equal("created");

    // Update state
    act(() => result.current.externalCb("updated"));
    expect(result.current.step).to.be.equal("updated");

    // Unmount
    act(() => unmount());
    // No update is expected here, I guess you could assert
    // nothing changed in the hook state
    expect(result.current.step).to.be.equal("updated");
  });
});

If you need to waitFor any updates though, the correct usage would be similar to the following:

import { act, renderHook, waitFor } from "@testing-library/react";
import { expect } from "chai";
import { useCustomHook } from "./useCustomHook";

describe("useCustomHook Hook", () => {
  it("step updates test", async () => {
    const { result, unmount } = renderHook(useCustomHook, {
      initialProps: "created",
    });

    expect(result.current.step).to.be.equal("created");

    act(() => result.current.externalCb("updated"));
    await waitFor(() => {
      expect(result.current.step).to.be.equal("updated");
    });

    act(() => unmount());
    await waitFor(() => {
      expect(result.current.step).to.be.equal("updated");
    });
  });
});

本文标签: javascriptCan39t successfully test a custom hook using useState and useEffectStack Overflow