skip to Main Content

Sorry if this seems really obvious, but I’ve been trying to figure out for two days now. I’m writing a simple unit test in vitest that renders a component to screen and then calling screen.debug(). The test is

describe('Login unit tests', () => {
    it('renders to screen', () => {
        const loginProps = {
            handleSubmit: (e: MouseEvent<HTMLButtonElement>) => Promise.resolve(console.log('handleSubmit')),
            usernameProps: {
                value: "",
                placeholder: "Username",
                onChange: (e: ChangeEvent<HTMLInputElement>) => console.log('changeEvent'),
                type: "text"
            },
            passwordProps: {
                value: "",
                placeholder: "Password",
                onChange: (e: ChangeEvent<HTMLInputElement>) => console.log('changeEvent'),
                type: "password" 
            },
            isHidden: true,
        }
        render(<Login {...loginProps} />);
        screen.debug();
    })
})

The function definitions are just stand-ins with correct type, but would most likely be a vi.fn(). The component definition including interface of prop object is

interface LoginProps {
    handleSubmit: (e: MouseEvent<HTMLButtonElement>) => Promise<void>,
    usernameProps: InputProps,
    passwordProps: InputProps,
    isHidden: boolean,
}

const Login = ({
    handleSubmit,
    usernameProps,
    passwordProps,
    isHidden
}: LoginProps) => {
    return (
        <div className="login-container">
            <div className="login-bounding-box">
                <div className="login-title">Login</div>
                <form className="login-form">
            <input {...usernameProps} />
            <input {...passwordProps} />
            <button type="submit" onClick={handleSubmit}>Login</button>
        </form>
                <div className="error-msg" hidden={isHidden}>Username/password incorrect.</div>
                <div className="signup-msg">
                    Don't have an account? Sign up <Link to="/signup">here</Link>.
                </div>
            </div>
        </div>
    )
}

After running npm run test I get the following error:

TypeError: Cannot destructure property 'basename' of 'React__namespace.useContext(...)' as it is null.
 ❯ LinkWithRef node_modules/react-router-dom/index.tsx:427:11
 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom.development.js:16305:18
 ❯ updateForwardRef node_modules/react-dom/cjs/react-dom.development.js:19226:20
 ❯ beginWork node_modules/react-dom/cjs/react-dom.development.js:21636:16
 ❯ beginWork$1 node_modules/react-dom/cjs/react-dom.development.js:27426:14
 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom.development.js:26560:12
 ❯ workLoopSync node_modules/react-dom/cjs/react-dom.development.js:26466:5
 ❯ renderRootSync node_modules/react-dom/cjs/react-dom.development.js:26434:7
 ❯ recoverFromConcurrentError node_modules/react-dom/cjs/react-dom.development.js:25850:20
 ❯ performConcurrentWorkOnRoot node_modules/react-dom/cjs/react-dom.development.js:25750:22

The interface for InputProps is

export default interface InputProps {
    value: string;
    placeholder: string | undefined;
    onChange: (e: ChangeEvent<HTMLInputElement>) => void;
    type: string;
}

I know the error is coming from the render(<Login />) line, but the error is cryptic and I’m not sure how to proceed. I assume there’s something wrong with how I’m passing in the props object to <Login /> but I can’t seem to figure out.

2

Answers


  1. Chosen as BEST ANSWER

    Thalles Cezar's answer is correct but just wanted to add for brevity that one usually uses MemoryRouter in tests and that render comes with a wrapper option so the problem can be fixed by changing to

    render(<Login {...loginProps} />, { wrapper: MemoryRouter })
    

  2. You’re probably wrapping a Router (like a BrowerRouter or MemoryRouter) somewhere in your app.
    But for the tests, the component is rendered by itself, without the rest of the app.

    In that case, it’s missing a Router, that React Router needs to find the basename property from its context.

    Try rendering your test wrapped in a router, like so

    import { BrowserRouter } from 'react-router-dom'
    ...
    render(<BrowserRouter><Login {...loginProps} /></BrowserRouter>);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search