reactjs - Apollo Graphql-ის ერთეული ტესტირება React-ში Hooks-ის გამოყენებით


ვცდილობ შევქმნა ერთეული ტესტი 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://stackoverflow.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 ტესტ რენდერის ნაცვლად, მაგრამ შედეგი იგივეა.

ჩემი დასკვნით, ეს შეცდომა გამოწვეულია იმით, რომ ვცვლი მიმდინარე სტატუსს ასინქრონული ფუნქციისა და Hooks-ის გამოყენებით. მაგრამ დარწმუნებული არ ვარ, რა შემიძლია განსხვავებულად გავაკეთო, რომ ეს მუშაობდეს.

პასუხი #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://stackoverflow.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();
    });
  });
});