Search code examples
javascriptreactjstypescriptjestjsreact-testing-library

Testing custom hook and context async method with react testing library


I'm trying to test a hook that uses a context underneath

The hook looks like this

const useConfirmation = () => {
  const { openDialog } = useContext(ConfirmationContext);

  const getConfirmation = ({ ...options }: DialogParams) => new Promise((res) => {
    openDialog({ actionCallback: res, ...options });
  });

  return { getConfirmation };
};

getConfirmation returns a promise that is resolved if the dialog is confirmed or not.

export const ConfirmationProvider = ({ children }: PropsWithChildren<{}>) => {
  const [dialogOpen, setDialogOpen] = useState(false);
  const [dialogConfig, setDialogConfig] = useState<DialogConfig>(initialState);

  const openDialog = (config: DialogConfig) => {
    setDialogOpen(true);
    setDialogConfig(config);
  };

  const resetDialog = () => {
    setDialogOpen(false);
    setDialogConfig(initialState);
  };

  const onConfirm = () => {
    resetDialog();
    dialogConfig.actionCallback(true);
  };

  const onDismiss = () => {
    resetDialog();
    dialogConfig.actionCallback(false);
  };

  const value = useMemo(() => ({ openDialog }), []);

  ...
};

When is going to be used it simply gets called like:

// Confirmed is a boolean
const connfirmed = await getConfirmation({...});

// confirmed ? 'Confirmed' : 'Canceled'

I'm able to test that the dialog closes or open but never the value of the state inside the context, how can I test this?

Currently my test looks like this:

  it('should open a confirmation dialog', async () => {
    const user = userEvent.setup();

    const { result } = renderHook(() => useConfirmation(), {
      wrapper: ({ children }) => (
        <AllTheProviders>
          <ConfirmationProvider>{children}</ConfirmationProvider>
        </AllTheProviders>
      ),
    });

    const confirmed = act(() => {
      result.current.getConfirmation({
        title: 'Confirmation Message',
        text: 'Confirmation Text',
        confirmText: 'Confirm',
        confirmColor: 'var(--quo-pink)',
      });
    });

    const modal = getByRole(document.body, 'dialog');

    expect(modal).toBeInTheDocument();

    await user.click(getByRole(document.body, 'button', { name: 'Cancel' }));

    expect(modal).not.toBeInTheDocument();
  });

I want to be able to say that confirmed is expected toBeTruthy or toBeFalsy but as it is right now its always {"then": [Function then]}.

Thank you in advance


Solution

  • Response was very straight forward. I simply needed to assign the return value to a variable outside.

    ...
    let confirmed;
    
    act(() => {
     confirmed = result.current.getConfirmation({
        title: 'Confirmation Message',
        text: 'Confirmation Text',
        confirmText: 'Confirm',
        confirmColor: 'var(--quo-pink)',
     });
    });
    
    expect(modal).toBeInTheDocument();
    
    await user.click(getByRole(document.body, 'button', { name: 'Cancel' }));
    await expect(confirmed).resolves.toBe(true);
    ...