使用 Hooks 在 React 中对 Apollo Graphql 进行单元测试

Posted

技术标签:

【中文标题】使用 Hooks 在 React 中对 Apollo Graphql 进行单元测试【英文标题】:Unit testing Apollo Graphql in React using Hooks 【发布时间】:2019-12-19 23:54:38 【问题描述】:

我正在尝试使用 React 和 Apollo Graphql 创建一个单元测试,但是我不断收到此错误:

Watch Usage: Press w to show more.  console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:104
    Warning: An update to ThemeHandler inside a test was not wrapped in act(...).

    When testing, code that causes React state updates should be wrapped into act(...):

    act(() => 
      /* fire events that update state */
    );
    /* assert on the output */

    This ensures that you're testing the behavior the user would see in the browser. 
        in ThemeHandler (at theme-handler.spec.tsx:51)
        in ApolloProvider (created by MockedProvider)
        in MockedProvider (at theme-handler.spec.tsx:50)

这是我的代码:

import  createMuiTheme, MuiThemeProvider  from '@material-ui/core';
import * as Sentry from '@sentry/browser';
import React,  useState  from 'react';
import  BrandTheme, useGetBrandThemeQuery  from '../../generated/graphql';

/**
 * Handles the app theme. Will set the default theme or the brand theme taken from the backend.
 */
export default function ThemeHandler(props: React.PropsWithChildren<any>): React.ReactElement 
  const brandId = Number(process.env.REACT_APP_BRAND);

  // Default Onyo theme
  const [theme, setTheme] = useState(
    palette: 
      primary:  main: '#f65a02' ,
      secondary:  main: '#520075' ,
    ,
    typography: 
      fontFamily: 'Quicksand, sans-serif',
    ,
  );

  useGetBrandThemeQuery(
    variables:  brandId ,
    skip: brandId <= 0,
    onCompleted: data => 
      if (
        !data.brandTheme ||
        !data.brandTheme.brandThemeColor ||
        data.brandTheme.brandThemeColor.length === 0
      ) 
        console.warn('Empty brand theme returned, using default');
        Sentry.captureMessage(`Empty brand theme for brandId: $brandId`, Sentry.Severity.Warning);
       else 
        const palette = parseBrandPalette(data.brandTheme as BrandTheme);
        setTheme( ...theme, palette );
        console.log('Theme', theme, data.brandTheme);
      
    ,
  );

  return <MuiThemeProvider theme=createMuiTheme(theme)>props.children</MuiThemeProvider>;


function parseBrandPalette(brandTheme: BrandTheme) 
  const pallete: any = ;

  for (const color of brandTheme.brandThemeColor!) 
    if (color && color.key === 'primaryColor') 
      pallete.primary =  main: color.value ;
     else if (color && color.key === 'darkPrimaryColor') 
      pallete.secondary =  main: color.value ;
    
  

  return pallete;

我的测试:

import renderer from 'react-test-renderer';
import React from 'react';
import ThemeHandler from './theme-handler';

import  MockedProvider, wait  from '@apollo/react-testing';
import  GetBrandThemeDocument  from '../../generated/graphql';
import  Button  from '@material-ui/core';

const  act  = renderer;

describe('Theme Handler', () => 
  const originalEnv = process.env;

  beforeEach(() => 
    // https://***.com/questions/48033841/test-process-env-with-jest/48042799
    jest.resetModules();
    process.env =  ...originalEnv ;
    delete process.env.REACT_APP_BRAND;
  );

  afterEach(() => 
    process.env = originalEnv;
  );

  it('should use a theme retrieved from the backend', async () => 
    process.env.REACT_APP_BRAND = '39';

    const mocks = [
      
        request: 
          query: GetBrandThemeDocument,
          variables:  brandId: 39 ,
        ,
        result: 
          data: 
            brandTheme: 
              brandThemeColor: [
                 key: 'primaryColor', value: '#182335' ,
                 key: 'darkPrimaryColor', value: '#161F2F' ,
              ],
            ,
          ,
        ,
      ,
    ];

    let wrapper;
    act(() => 
      wrapper = renderer.create(
        <MockedProvider mocks=mocks addTypename=false>
          <ThemeHandler>
            <Button color='primary' id='test-obj'>
              Hello world!
            </Button>
          </ThemeHandler>
        </MockedProvider>
      );
    );

    await wait(0);
    expect(wrapper).toBeTruthy();
  );
);

我也试过用 Enzyme 的 mount 代替 React 测试渲染器,但结果是一样的。

据我所知,这个错误是因为我正在使用异步函数和钩子更改当前状态而引起的。但我不确定我可以做些什么不同的事情来实现这一点。

【问题讨论】:

【参考方案1】:

我通过使用act 包装测试中的所有内容解决了我的问题。我认为发生此错误是因为部分测试包含在 act 中,但异步部分未包含,因此更改发生在此函数范围之外。

这是更新的测试,即通过:

import React from 'react';
import ThemeHandler from './theme-handler';

import  MockedProvider, wait  from '@apollo/react-testing';
import  GetBrandThemeDocument  from '../../generated/graphql';
import  Button  from '@material-ui/core';
import  mount  from 'enzyme';
import  act  from 'react-dom/test-utils';

describe('Theme Handler', () => 
  const originalEnv = process.env;

  beforeEach(() => 
    // https://***.com/questions/48033841/test-process-env-with-jest/48042799
    jest.resetModules();
    process.env =  ...originalEnv ;
    delete process.env.REACT_APP_BRAND;
  );

  afterEach(() => 
    process.env = originalEnv;
  );

  it('should use a theme retrieved from the backend', async () => 
    process.env.REACT_APP_BRAND = '39';

    await act(async () => 
      const mocks = [
        
          request: 
            query: GetBrandThemeDocument,
            variables:  brandId: 39 ,
          ,
          result: 
            data: 
              brandTheme: 
                brandThemeColor: [
                   key: 'primaryColor', value: '#182335' ,
                   key: 'darkPrimaryColor', value: '#161F2F' ,
                ],
              ,
            ,
          ,
        ,
      ];

      const wrapper = mount(
        <MockedProvider mocks=mocks addTypename=false>
          <ThemeHandler>
            <Button color='primary' id='test-obj'>
              Hello world!
            </Button>
          </ThemeHandler>
        </MockedProvider>
      );

      expect(wrapper).toBeTruthy();

      await wait(0);

      wrapper.update();
      expect(wrapper.find('#test-obj')).toBeTruthy();
    );
  );
);

【讨论】:

以上是关于使用 Hooks 在 React 中对 Apollo Graphql 进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

react---Hooks的基本使用---巷子

react自定义hooks-自动改变页面的title,Http请求hooks等..(持续更新)

React Native Hooks开发指南

React Native Hooks开发指南

React Native Hooks开发指南

React Native Hooks开发指南