2

I am trying to create a unit test using React and Apollo Graphql, however I keep getting this error:

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)

Here is my code:

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;
}

And my test:

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();
  });
});

I also tried to use Enzyme's mount instead of the React test renderer, but the result is the same.

As far as I could tell, this error is being caused because I am changing the current state using an async function and hooks. But I am not sure what could I do differently for this to work.

Felipe
  • 6,312
  • 11
  • 52
  • 70

1 Answers1

3

I solved my problem by wrapping everything on my test with act. I believe that this error was happening because part of the test was wrapped in act, but the async part wasn't, so the change was happening outside the scope of this function.

Here is the updated test, that is passing:

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();
    });
  });
});
Felipe
  • 6,312
  • 11
  • 52
  • 70