Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing a component while using ThemeProvider #1319

Closed
gpietro opened this issue Nov 21, 2017 · 26 comments
Closed

Testing a component while using ThemeProvider #1319

gpietro opened this issue Nov 21, 2017 · 26 comments

Comments

@gpietro
Copy link

gpietro commented Nov 21, 2017

I have been reading the test utilities section of the documentation but I didn't find any information on how to test a component with Jest that is wrapped inside a ThemeProvider. The only solution I have found is to pass a theme props to the component, but this imply to pass it also to all the component children and I would like to avoid it.

Here is my SO question with details on my case.

Thanks

@k15a
Copy link
Member

k15a commented Nov 21, 2017

Just wrap the component in a ThemeProvider in the Jest test file.

@gpietro
Copy link
Author

gpietro commented Nov 21, 2017

With enzyme then I can't access to the component I'm interested in, since some operation works only on the root, in this case it would be ThemeProvider...

@pesterev
Copy link

pesterev commented Nov 21, 2017

You can wrap your components in a ThemeProvider with some theme

import React from 'react'
import { shallow, mount } from 'enzyme'
import { ThemeProvider } from 'styled-components'
import { theme } from '<path_to_theme>'

function wrapWithTheme (fn, children, options) {
  const wrapper = fn(
    <ThemeProvider theme={theme}>
      { children }
    </ThemeProvider>,
    options
  )

  return wrapper[fn.name]({
    context: wrapper.instance().getChildContext()
  })
}

export function shallowWithTheme () {
  return wrapWithTheme(shallow, ...arguments)
}

export function mountWithTheme () {
  return wrapWithTheme(mount, ...arguments)
}

UPDATE: This approach doesn't work anymore. PLEASE DON'T USE IT.

@kitten
Copy link
Member

kitten commented Nov 21, 2017

There was an entire issue dedicated to this question: styled-components/jest-styled-components#61

There is also a link to an issue on this repo in there that has more suggestions.

But this issue was resolved with an addition to the readme there that seemingly hasn’t been added to our site yet (PRs welcome 🙂)

@kitten kitten closed this as completed Nov 21, 2017
@galina-niukhalova
Copy link

Is there any solution to wrap shallow/mount with Theme in styled-components version 4?

I used @VladimirPesterev solution and it worked in the previous version.
After upgrading styled-components to v4.0.2, I have an error:
TypeError: wrapper.instance(...).getChildContext is not a function

@arka-na
Copy link

arka-na commented Oct 24, 2018

@galina-niukhalova a (hackish) solution I've found is setting the context directly on the ThemeConsumer as follow:

import { ThemeConsumer } from 'styled-components'
import defaultTheme from '../somewhere/theme'

export const shallowWithTheme = (children, theme = defaultTheme) => {
  ThemeConsumer._currentValue = theme
  return shallow(children)
}

@galina-niukhalova
Copy link

@arka-na Thank you for your answer.
Unfortunately, your method doesn't work for me, I got an error
Cannot set property '_currentValue' of underfined

But I found another solution, which works for me.
Just set context in shallow / mount, like

export const shallowWithTheme = (children, themeForTests) => {
  return shallow(children, { theme: themeForTests }) //the same with mount
}

https://airbnb.io/enzyme/docs/api/ShallowWrapper/setContext.html

@jitenderchand1
Copy link

jitenderchand1 commented Nov 8, 2018

Facing the same issue.

following was working fine with version 2

export const shallowWithTheme = (children, options) => {
  const wrapper = shallow(<ThemeProvider theme={theme}>{children}</ThemeProvider>, options);
  const instance = wrapper.root().instance();
  return wrapper.shallow({ context: instance.getChildContext() }).dive();
};

But below one is not working after making change . Following example is using v4

export const shallowWithTheme = (children) => {
  ThemeConsumer._currentValue = theme;
  return shallow(children).dive();
};

I need to check the props of child component hence I need to use .dive

test case

import React from 'react';
import SomeComponent from './';
import { shallowWithTheme } from '../../helper';


describe('SomeComponent component', () => {
   it('should enable the search button while the input is not empty', () => {
    const wrapper = shallowWithTheme(<OrderSearch />);
    wrapper.setState({ searchTerm: '111' });
    expect(wrapper.find('SearchButton').props().isDisabled).toBe(false);
  });
});

it is not able to find the props of SearchButton. debug screenshot

screen shot 2018-11-08 at 5 25 50 pm

@karolisgrinkevicius
Copy link

karolisgrinkevicius commented Nov 16, 2018

@jitenderchand1 In either way you are trying to access SearchButton wrongly. Try this:

wrapper.find('Styled(SearchButton)')

@svey
Copy link

svey commented Nov 16, 2018

Has anyone found a way to test ThemeProvider with v4? While @arka-na 's solution works but it's not adequate for my test purpose.

@cneeson
Copy link

cneeson commented Dec 13, 2018

Bump on the above, has anyone managed to get a working solution for version 4 of styled-components?

@developer239
Copy link

+1

@erikdstock
Copy link

erikdstock commented Jan 24, 2019

Assuming we want consistent access to one theme, Is there any reason not to apply @arka-na 's solution above in test setup? in my jest setupTests.js I have added simply

  ThemeConsumer._currentValue = theme

@nlopin
Copy link

nlopin commented Feb 22, 2019

The main reason: _currentValue is an internal field and can be changed in any version without a warning

@csvan
Copy link

csvan commented Feb 22, 2019

There is ongoing work at Enzyme to bring full support for the Context API in shallow renders. See references from styled-components/jest-styled-components#191 (comment)

@asmyshlyaev177
Copy link

For new versions like this

function wrapWithTheme(children, options) {
  ThemeConsumer._context._currentValue2 = themeForTests
  const wrapper = shallow(
    <ThemeProvider theme={theme}>{children}</ThemeProvider>,
    options
  )

@Luavis
Copy link

Luavis commented Aug 16, 2019

I fix this problem following with answer in here
wrappingComponent option in enzyme mount could be answer.

export function mountWithTheme(child) {
    return mount(child, {
        wrappingComponent: ({ children }) => <ThemeProvider theme={TEST_THEME}>{children}</ThemeProvider>,
    });
}

export function shallowWithTheme(child) {
    return shallow(child, {
        wrappingComponent: ({ children }) => <ThemeProvider theme={TEST_THEME}>{children}</ThemeProvider>,
    });
}

@vinodloha
Copy link

I fix this problem following with answer in here
wrappingComponent option in enzyme mount could be answer.

export function mountWithTheme(child) {
    return mount(child, {
        wrappingComponent: ({ children }) => <ThemeProvider theme={TEST_THEME}>{children}</ThemeProvider>,
    });
}

export function shallowWithTheme(child) {
    return shallow(child, {
        wrappingComponent: ({ children }) => <ThemeProvider theme={TEST_THEME}>{children}</ThemeProvider>,
    });
}

This works like a charm 👍 Thanks

@hamishtaplin
Copy link

hamishtaplin commented May 7, 2020

I fix this problem following with answer in here
wrappingComponent option in enzyme mount could be answer.

export function mountWithTheme(child) {
    return mount(child, {
        wrappingComponent: ({ children }) => <ThemeProvider theme={TEST_THEME}>{children}</ThemeProvider>,
    });
}

export function shallowWithTheme(child) {
    return shallow(child, {
        wrappingComponent: ({ children }) => <ThemeProvider theme={TEST_THEME}>{children}</ThemeProvider>,
    });
}

This works like a charm 👍 Thanks

Am I the only person this doesn't work for? mount works fine but components rendered with shallow don't receive the theme as props.

I have a feeling this is related to shallow not working with useContext. See here for some discussion of this:

@faizrktm
Copy link

If you are using react-testing-library, this approach is considerable https://testing-library.com/docs/react-testing-library/setup#custom-render

@slinkardbrandon
Copy link

I do like @faizrktm's suggestion for overriding the render method if you use react-testing-library and I would suggest going that route, however if you would prefer a solution similar to the others suggested in this thread you could also achieve it using something like the following:

import { render } from '@testing-library/react';

function renderWithWrapper(component, options) {
  const Wrapper = ({ children }) => (
    <ThemeProvider theme={TEST_THEME}>
      {children}
    </ThemeProvider>
  );

  return render(component, { wrapper: Wrapper, ...options });
}

@totszwai
Copy link

In case someone stumble upon this thread... the shallowWithTheme does not work with Enzyme if you are including <GlobalStyle /> or maybe even <CssBaseline /> inside your ThemeProvider.
enzymejs/enzyme#2549

@LuisEspinosa7
Copy link

Hello, I've jst came across with the following solution, I'm using Styled Components 5.3.3, this is a simple example, just to test the snapshot of a component, but it could be used for any scenario, the trick is to render the component and wrap it with the ThemeProvider, obviously passing through your theme configuration:

`import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-enzyme';
import 'jest-styled-components';
import { ThemeProvider } from 'styled-components';
import { theme } from '../../../themes/standard';
import { StyledButton } from '../../../components/styles/Button.styles';
import { render } from 'enzyme/build';

describe("Component: StyledButton", () => {

it('should render', () => {
    const { fragment } = render(
        <ThemeProvider theme={theme}>
            <StyledButton />
        </ThemeProvider>
    )
    const tree = renderer.create(fragment).toJSON();
    expect(tree).toMatchSnapshot();
})

})`

@LuisEspinosa7
Copy link

@galina-niukhalova a (hackish) solution I've found is setting the context directly on the ThemeConsumer as follow:

import { ThemeConsumer } from 'styled-components'
import defaultTheme from '../somewhere/theme'

export const shallowWithTheme = (children, theme = defaultTheme) => {
  ThemeConsumer._currentValue = theme
  return shallow(children)
}

It works!!!

@abdulrahimiliasu
Copy link

I just created a function for that instead 🤷

import "@testing-library/jest-dom";
import { render } from "@testing-library/react";
import { ThemeProvider } from "styled-components";
import { Themes } from "@theme/Theme";
import { ITheme } from "@abstractions/theme/ITheme";

const TEST_THEME = Themes.light;

export function renderWithTheme(children: React.ReactElement, theme?: ITheme) {
   return render(<ThemeProvider theme={theme ? theme : TEST_THEME}>{children}</ThemeProvider>);
}

@gabrielbull
Copy link

@abdulrahimiliasu This will make it better as you now use the wrapper option of testing-lib and snapshots will be rendered without the theme provider

import "@testing-library/jest-dom";
import { render, RenderOptions } from "@testing-library/react";
import { ThemeProvider } from "styled-components";
import { Themes } from "@theme/Theme";
import { ITheme } from "@abstractions/theme/ITheme";

const TEST_THEME = Themes.light;

function createWrapper(theme?: ITheme) {
  return function Wrapper({ children }: PropsWithChildren): ReactElement | null {
     return <ThemeProvider theme={theme ? theme : TEST_THEME}>{children}</ThemeProvider>;
  }
}

interface RenderWithThemeOptions<
  Q extends Queries = typeof queries,
  Container extends Element | DocumentFragment = HTMLElement,
  BaseElement extends Element | DocumentFragment = Container,
> extends RenderOptions<Q, Container, BaseElement> {
  theme?: ITheme
}

export function renderWithTheme(children: React.ReactElement, options?: RenderWithThemeOptions) {
  const { theme, ...rest } = { ...options };
  return render(children, { wrapper: createWrapper(theme), ...rest });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests