3

I've setup a simple API endpoint with NextJS and want to be able to implement some unit tests for it.

The endpoint uses Google recaptcha to protect the site (and the site owner's email) from bot spamming.

The endpoint is seemingly working as expected however I feel like the method I have used in order to enable me to unit test it is somewhat hacky.

The basic gist of my solution is to simply check if the NODE_ENV is set to test and return a generic success JSON if it is:

  if (process.env.NODE_ENV === "test")
    return {
      success: true,
      challenge_ts: new Date().getTime(),
      error_codes: [],
      hostname: "localhost",
    };

If possible I would prefer to not ship the app with this code in the endpoint's module file as it just doesn't sit right with me.

The issue is that removing this code obviously means my tests all fail as they will return a status of 422 due to the lack of a response token from the Google ReCaptcha API.

I am using the react-google-recaptcha NPM package to implement ReCaptcha and have setup my jest config to use the .env.test files when running tests.

This allows me to use the suggested keys from Google's docs without any need for changing my config etc.

The issue I am facing is an inability to get the response token from the frontend implementation of ReCaptcha to then pass on to the NextJS API endpoint.

I have tried to use render and createRef mimicking my actual frontend implementation but within Jest to no avail.

Does anyone have a better solution to that which I have currently implemented?

Thanks in advance.

Love.

MakingStuffs
  • 666
  • 1
  • 9
  • 17

4 Answers4

4

in addition to @Saurabh's answer, make sure you put this code snippet to the jest.setup.js file.
I've also slightly changed the implementation:

jest.mock('react-google-recaptcha', () => {
  const RecaptchaV2 = React.forwardRef((props, ref) => {
    React.useImperativeHandle(ref, () => ({
      reset: jest.fn(),
      execute: jest.fn(),
      executeAsync: jest.fn(() => 'token'),
    }));
    return <input ref={ref} type="checkbox" data-testid="mock-v2-captcha-element" {...props} />;
  });

  return RecaptchaV2;
});
Vlad R
  • 1,851
  • 15
  • 13
2

I have added this jest mock in our codebase to mock the react-google-recaptcha library's behaviour.

jest.mock('react-google-recaptcha', () => {
  const React = require('react');
  const RecaptchaV2 = React.forwardRef((props, ref) => {
    React.useImperativeHandle(ref, () => ({
      reset: jest.fn(),
      execute: () => {
        props.onChange('test-v2-token');
      }
    }));
    return <input ref={ref} data-testid="mock-v2-captcha-element" {...props} />
  });

  return RecaptchaV2;
});

This will mock the reset and execute function in the library. The execute function will always pass test-v2-token at place of v2 captcha token.

0

Easy implementation with Typescript, I change the onChange behavior to onClick here which updates the state in my comp:

/* eslint-disable react/display-name */
jest.mock('react-google-recaptcha', () => {
    const ReCAPTCHA = React.forwardRef(
        (props: { [key: string]: unknown | any }, ref: React.LegacyRef<HTMLInputElement> | undefined): JSX.Element => {
            return (
                <>
                    <input type="checkbox" data-testid="mock-recaptcha" {...props} onClick={props.onChange} ref={ref} />
                </>
            )
        }
    )
    return ReCAPTCHA
})

Happy coding

Ericgit
  • 6,089
  • 2
  • 42
  • 53
  • jest.mock rejects this complaining that it can't access external variables. It doesn't like the use of `React`. – aikimcr May 09 '23 at 19:25
0

Not directly related to the original question but for anyone stumbling upon this that uses next-recaptcha-v3 the below works:

jest.mock('next-recaptcha-v3', () => ({
  ReCaptchaProvider: ({ children }) => children,
  useReCaptcha: () => ({
    executeRecaptcha: jest.fn(() => 'recaptcha-token'),
  }),
}))
tctc91
  • 1,343
  • 2
  • 21
  • 41