在提供的上下文中调用而不是 Jest 模拟的实现函数

Implemented function in provided context called instead of Jest mock

我需要测试在调用上下文提供的函数的 React 应用程序中单击按钮。在 Jest 测试中调用上下文提供的模拟函数时,将调用实际函数。如何解决此问题?

申请App.tsx

import React, {useContext, useState} from 'react'
import styles from './App.module.scss'
import {ThemeContext} from './context/theme/ThemeProvider'

export const App = (): JSX.Element => {
  const {changeTheme, activeTheme} = useContext(ThemeContext)
  const [theme, setTheme] = useState<string>(activeTheme)

  const setNewTheme = (newTheme: string) => {
    changeTheme(newTheme)
    setTheme(newTheme)
  }

  return (
    <div className={styles.container}>
      <h1>Project Name</h1>
      <button
        data-testid='app-theme-btn1'
        onClick={() => {
          setNewTheme('dark')
        }}
      >
        Click here
      </button>
      <p>{`Active Theme: ${theme}`}</p>
    </div>
  )
}

实测

  test('click button changes theme', async () => {
    const defaultTheme = 'light'
    type ThemeContext = {
      activeTheme: string
      changeTheme: (theme: string) => void
    }

    const defaultThemeContext: ThemeContext = {
      activeTheme: defaultTheme,
      changeTheme: (theme) => {
        /* left empty */
      }
    }

    const ThemeContext = createContext<ThemeContext>(defaultThemeContext)

    const themeContext = {
      activeTheme: defaultTheme,
      changeTheme: jest.fn().mockImplementation((newTheme) => {
        themeContext.activeTheme = newTheme
      })
    }

    render(
      <ThemeContext.Provider value={themeContext}>
        <App />
      </ThemeContext.Provider>
    )
    screen.debug(undefined, Infinity)
    const themeButton = screen.getByTestId('app-theme-btn1')
    await userEvent.click(themeButton)
    await waitFor(() => screen.queryByText('Active Theme: dark'))
    // Error: expect(jest.fn()).toHaveBeenCalledTimes(expected)
    //
    // Expected number of calls: 1
    // Received number of calls: 0
    expect(themeContext.changeTheme).toHaveBeenCalledTimes(1)
    screen.debug(undefined, Infinity)
  })

问题是您在测试用例中创建了不同的 React 上下文,而不是使用在 ./context/theme/ThemeProvider 模块中创建的 ThemeContext

也就是说useContext() hook在App组件中使用的context应该是ThemeContext,这样App组件才能接收context值并订阅值从 ThemeContext.Provider.

变化

例如

app.tsx:

import React, { useContext, useState } from 'react';
import { ThemeContext } from './theme-provider';

export const App = (): JSX.Element => {
  const { changeTheme, activeTheme } = useContext(ThemeContext);
  const [theme, setTheme] = useState<string>(activeTheme);

  const setNewTheme = (newTheme: string) => {
    changeTheme(newTheme);
    setTheme(newTheme);
  };

  return (
    <div>
      <h1>Project Name</h1>
      <button
        data-testid="app-theme-btn1"
        onClick={() => {
          setNewTheme('dark');
        }}
      >
        Click here
      </button>
      <p>{`Active Theme: ${theme}`}</p>
    </div>
  );
};

theme-provider.tsx:

import React, { useState } from 'react';

const defaultContext = {
  activeTheme: 'light',
  changeTheme: (newTheme: string) => {},
};
export const ThemeContext = React.createContext(defaultContext);

export const ThemeProvider = ({ children }) => {
  const [activeTheme, setActiveTheme] = useState(defaultContext.activeTheme);
  const changeTheme = (newTheme: string) => {
    setActiveTheme(newTheme);
  };
  return <ThemeContext.Provider value={{ activeTheme, changeTheme }}>{children}</ThemeContext.Provider>;
};

app.test.tsx:

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { App } from './app';
import { ThemeContext } from './theme-provider';

describe('71901699', () => {
  test('should pass', async () => {
    const defaultTheme = 'light';
    const themeContext = {
      activeTheme: defaultTheme,
      changeTheme: jest.fn().mockImplementation((newTheme) => {
        themeContext.activeTheme = newTheme;
      }),
    };

    render(
      <ThemeContext.Provider value={themeContext}>
        <App />
      </ThemeContext.Provider>
    );
    const themeButton = screen.getByTestId('app-theme-btn1');
    userEvent.click(themeButton);
    expect(themeContext.changeTheme).toHaveBeenCalledTimes(1);
    await waitFor(() => screen.queryByText('Active Theme: dark'));
  });
});

测试结果:

 PASS  Whosebug/71901699/app.test.tsx (8.691 s)
  71901699
    ✓ should pass (74 ms)

--------------------|---------|----------|---------|---------|-------------------
File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------------|---------|----------|---------|---------|-------------------
All files           |      80 |      100 |      50 |   77.78 |                   
 app.tsx            |     100 |      100 |     100 |     100 |                   
 theme-provider.tsx |   55.56 |      100 |       0 |      50 | 10-14             
--------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        9.217 s