title: Can't get Jest to work with Styled Components which contain theming
body: <h1>The Problem</h1>
  
  <p>I've been using <a href="https://facebook.github.io/jest/" rel="noreferrer">Jest</a> and <a href="https://github.com/airbnb/enzyme" rel="noreferrer">Enzyme</a> to write tests for my React components build with the awesome <a href="https://www.styled-components.com/" rel="noreferrer">Styled Components</a> library.</p>
  
  <p>However, since I've implemented theming all my tests are breaking. Let me give you an example. </p>
  
  <p>This is the code of my <code>LooksBrowser</code> component (I've removed all of my imports and prop-types to make it a little more readable):</p>
  
  <pre><code>const LooksBrowserWrapper = styled.div`
    position: relative;
    padding: 0 0 56.25%;
  `;
  
  const CurrentSlideWrapper = styled.div`
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
  `;
  
  const NextSlideWrapper = CurrentSlideWrapper.extend`
    z-index: 1;
  `;
  
  const SlideImage = styled.img`
    display: block;
    width: 100%;
  `;
  
  const SlideText = styled.div`
    display: flex;
    position: absolute;
    top: 25%;
    left: ${PXToVW(72)};
    height: 25%;
    flex-direction: column;
    justify-content: center;
  `;
  
  const SlideTitle = styled.p`
    flex: 0 0 auto;
    text-transform: uppercase;
    line-height: 1;
    color: ${props =&gt; props.color};
    font-family: ${props =&gt; props.theme.LooksBrowser.SlideTitle.FontFamily};
    font-size: ${PXToVW(52)};
  `;
  
  const SlideSubtitle = SlideTitle.extend`
    font-family: ${props =&gt; props.theme.LooksBrowser.SlideSubtitle.FontFamily};
  `;
  
  export default class LooksBrowser extends React.Component {
    state = {
      currentSlide: {
        imageURL: this.props.currentSlide.imageURL,
        index: this.props.currentSlide.index,
        subtitle: this.props.currentSlide.subtitle,
        textColor: this.props.currentSlide.textColor,
        title: this.props.currentSlide.title
      },
      nextSlide: {
        imageURL: this.props.nextSlide.imageURL,
        index: this.props.nextSlide.index,
        subtitle: this.props.nextSlide.subtitle,
        textColor: this.props.nextSlide.textColor,
        title: this.props.nextSlide.title
      },
      nextSlideIsLoaded: false
    };
  
    componentDidMount() {
      this.setVariables();
    }
  
    componentWillReceiveProps(nextProps) {
      // Only update the state when the nextSlide data is different than the current nextSlide data
      // and when the LooksBrowser component isn't animating
      if (this.props.nextSlide.imageURL !== nextProps.nextSlide.imageURL &amp;&amp; !this.isAnimating) {
        this.setState(prevState =&gt; update(prevState, {
          nextSlide: {
            imageURL: { $set: nextProps.nextSlide.imageURL },
            index: { $set: nextProps.nextSlide.index },
            subtitle: { $set: nextProps.nextSlide.subtitle },
            textColor: { $set: nextProps.nextSlide.textColor },
            title: { $set: nextProps.nextSlide.title }
          }
        }));
      }
    }
  
    componentDidUpdate() {
      if (!this.isAnimating) {
        if (this.state.nextSlide.imageURL !== '' &amp;&amp; this.state.nextSlideIsLoaded) {
          // Only do the animation when the nextSlide is done loading and it defined inside of the state
          this.animateToNextSlide();
        } else if (this.state.currentSlide.imageURL !== this.props.nextSlide.imageURL &amp;&amp; this.state.nextSlide.imageURL !== this.props.nextSlide.imageURL) {
          // This usecase is for when the LooksBrowser already received another look while still being in an animation
          // After the animation is done it checks if the new nextSlide data is different than the current currentSlide data
          // And also checks if the current nextSlide state data is different than the new nextSlide data
          // If so, it updates the nextSlide part of the state so that in the next render animateToNextSlide will be called
          this.setState(prevState =&gt; update(prevState, {
            nextSlide: {
              imageURL: { $set: this.props.nextSlide.imageURL },
              index: { $set: this.props.nextSlide.index },
              subtitle: { $set: this.props.nextSlide.subtitle },
              textColor: { $set: this.props.nextSlide.textColor },
              title: { $set: this.props.nextSlide.title }
            }
          }));
        } else if (!this.state.nextSlideIsLoaded) {
          // Reset currentSlide position to prevent 'flash'
          TweenMax.set(this.currentSlide, {
            x: '0%'
          });
        }
      }
    }
  
    setVariables() {
      this.TL = new TimelineMax();
      this.isAnimating = false;
    }
  
    nextSlideIsLoaded = () =&gt; {
      this.setState(prevState =&gt; update(prevState, {
        nextSlideIsLoaded: { $set: true }
      }));
    };
  
    animateToNextSlide() {
      const AnimateForward = this.state.currentSlide.index &lt; this.state.nextSlide.index;
      this.isAnimating = true;
  
      this.TL.clear();
      this.TL
        .set(this.currentSlide, {
          x: '0%'
        })
        .set(this.nextSlide, {
          x: AnimateForward ? '100%' : '-100%'
        })
        .to(this.currentSlide, 0.7, {
          x: AnimateForward ? '-100%' : '100%',
          ease: Quad.easeInOut
        })
        .to(this.nextSlide, 0.7, {
          x: '0%',
          ease: Quad.easeInOut,
          onComplete: () =&gt; {
            this.isAnimating = false;
            this.setState(prevState =&gt; update(prevState, {
              currentSlide: {
                imageURL: { $set: prevState.nextSlide.imageURL },
                index: { $set: prevState.nextSlide.index },
                subtitle: { $set: prevState.nextSlide.subtitle },
                textColor: { $set: prevState.nextSlide.textColor },
                title: { $set: prevState.nextSlide.title }
              },
              nextSlide: {
                imageURL: { $set: '' },
                index: { $set: 0 },
                subtitle: { $set: '' },
                textColor: { $set: '' },
                title: { $set: '' }
              },
              nextSlideIsLoaded: { $set: false }
            }));
          }
        }, '-=0.7');
    }
  
    render() {
      return(
        &lt;LooksBrowserWrapper&gt;
          &lt;CurrentSlideWrapper innerRef={div =&gt; this.currentSlide = div} &gt;
            &lt;SlideImage src={this.state.currentSlide.imageURL} alt={this.state.currentSlide.title} /&gt;
            &lt;SlideText&gt;
              &lt;SlideTitle color={this.state.currentSlide.textColor}&gt;{this.state.currentSlide.title}&lt;/SlideTitle&gt;
              &lt;SlideSubtitle color={this.state.currentSlide.textColor}&gt;{this.state.currentSlide.subtitle}&lt;/SlideSubtitle&gt;
            &lt;/SlideText&gt;
          &lt;/CurrentSlideWrapper&gt;
          {this.state.nextSlide.imageURL &amp;&amp;
            &lt;NextSlideWrapper innerRef={div =&gt; this.nextSlide = div}&gt;
              &lt;SlideImage src={this.state.nextSlide.imageURL} alt={this.state.nextSlide.title} onLoad={this.nextSlideIsLoaded} /&gt;
              &lt;SlideText&gt;
                &lt;SlideTitle color={this.state.nextSlide.textColor}&gt;{this.state.nextSlide.title}&lt;/SlideTitle&gt;
                &lt;SlideSubtitle color={this.state.nextSlide.textColor}&gt;{this.state.nextSlide.subtitle}&lt;/SlideSubtitle&gt;
              &lt;/SlideText&gt;
            &lt;/NextSlideWrapper&gt;
          }
        &lt;/LooksBrowserWrapper&gt;
      );
    }
  }
  </code></pre>
  
  <p>Then now my tests for my <code>LooksBrowser</code> component (following is the full code):</p>
  
  <pre><code>import React from 'react';
  import Enzyme, { mount } from 'enzyme';
  import renderer from 'react-test-renderer';
  import Adapter from 'enzyme-adapter-react-16';
  import 'jest-styled-components';
  import LooksBrowser from './../src/app/components/LooksBrowser/LooksBrowser';
  
  Enzyme.configure({ adapter: new Adapter() });
  
  test('Compare snapshots', () =&gt; {
    const Component = renderer.create(&lt;LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} /&gt;);
  
    const Tree = Component.toJSON();
    expect(Tree).toMatchSnapshot();
  });
  
  test('Renders without crashing', () =&gt; {
    mount(&lt;LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} /&gt;);
  });
  
  test('Check if componentDidUpdate gets called', () =&gt; {
    const spy = jest.spyOn(LooksBrowser.prototype, 'componentDidUpdate');
    const Component = mount(&lt;LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} /&gt;);
  
    Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
    expect(spy).toBeCalled();
  });
  
  test('Check if animateToNextSlide gets called', () =&gt; {
    const spy = jest.spyOn(LooksBrowser.prototype, 'animateToNextSlide');
    const Component = mount(&lt;LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} /&gt;);
  
    Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
    Component.setState({ nextSlideIsLoaded: true });
    expect(spy).toBeCalled();
  });
  </code></pre>
  
  <p>Before I implemented theming all of these tests were passing. After I implemented theming I get the following error from every test:</p>
  
  <pre><code>TypeError: Cannot read property 'SlideTitle' of undefined
  
    44 |   line-height: 1;
    45 |   color: ${props =&gt; props.color};
  &gt; 46 |   font-family: ${props =&gt; props.theme.LooksBrowser.SlideTitle.FontFamily};
    47 |   font-size: ${PXToVW(52)};
    48 | `;
    49 |
  </code></pre>
  
  <p>Ok, makes sense. The theme is not defined. </p>
  
  <h1>Solution One</h1>
  
  <p>So after some googling I found the following 'solution': </p>
  
  <p><a href="https://github.com/styled-components/jest-styled-components#theming" rel="noreferrer">https://github.com/styled-components/jest-styled-components#theming</a></p>
  
  <blockquote>
    <p>The recommended solution is to pass the theme as a prop:
    <code>const wrapper = shallow(&lt;Button theme={theme} /&gt;)</code></p>
  </blockquote>
  
  <p>So I add the following code to my <code>LooksBrowser</code> test file: </p>
  
  <pre><code>const theme = {
    LooksBrowser: {
      SlideTitle: {
        FontFamily: 'Futura-Light, sans-serif'
      },
      SlideSubtitle: {
        FontFamily: 'Futura-Demi, sans-serif'
      }
    }
  };
  </code></pre>
  
  <p>And edit all my tests to pass the theme manually. For example:</p>
  
  <pre><code>test('Compare snapshots', () =&gt; {
    const Component = renderer.create(&lt;LooksBrowser theme={theme} currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} /&gt;);
  
    const Tree = Component.toJSON();
    expect(Tree).toMatchSnapshot();
  });
  </code></pre>
  
  <p>After I've done this I run my tests again. Still the same error occurs.</p>
  
  <h1>Solution Two</h1>
  
  <p>I decided to wrap my components inside of the Styled Components <a href="https://www.styled-components.com/docs/advanced#theming" rel="noreferrer">ThemeProvider</a>. This fixes the errors from my <code>Compare snapshots</code> and <code>Renders without crashing</code> tests. </p>
  
  <p>However, since I'm also changing the props/state of my <code>LooksBrowser</code> component and testing the results, this doesn't work anymore. This is because the <code>setProps</code> and <code>setState</code> functions only can be used on the root/wrapper component.</p>
  
  <p>So wrapping my components in the <code>ThemeProvider</code> component isn't a valid solution either.</p>
  
  <h1>Solution Three</h1>
  
  <p>I decided to try the log the props of one of my Styled Components. So I changed my <code>SlideTitle</code> subcomponent to this:</p>
  
  <pre><code>const SlideTitle = styled.p`
    flex: 0 0 auto;
    text-transform: uppercase;
    line-height: 1;
    color: ${props =&gt; {
      console.log(props.theme.LooksBrowser.SlideTitle.FontFamily);
      return props.color;
    }};
    font-family: ${props =&gt; props.theme.LooksBrowser.SlideTitle.FontFamily};
    font-size: ${PXToVW(52)};
  `;
  </code></pre>
  
  <p>I get the following error:</p>
  
  <pre><code>TypeError: Cannot read property 'SlideTitle' of undefined
  
    44 |   line-height: 1;
    45 |   color: ${props =&gt; {
  &gt; 46 |     console.log(props.theme.LooksBrowser.SlideTitle.FontFamily);
    47 |     return props.color;
    48 |   }};
    49 |   font-family: ${props =&gt; props.theme.LooksBrowser.SlideTitle.FontFamily};
  </code></pre>
  
  <p>Ok, seems like the entire theme prop is just empty. Let's try manually passing the theme to the <code>SlideTitle</code> (which is a horrendous solution btw, it would mean I need to pass my theme manually to every Styled Component in my entire project).</p>
  
  <p>So I added the following code:</p>
  
  <pre><code>&lt;SlideTitle theme={this.props.theme} color{this.state.currentSlide.textColor}&gt;{this.state.currentSlide.title}&lt;/SlideTitle&gt;
  </code></pre>
  
  <p>And I run my tests again. I see the following line in my terminal:</p>
  
  <pre><code>console.log src/app/components/LooksBrowser/LooksBrowser.js:46
  Futura-Light, sans-serif
  </code></pre>
  
  <p>Yeah, this is what I'm looking for! I scroll down and see the same error again... <em>yikes</em>.</p>
  
  <h1>Solution Four</h1>
  
  <p>In the <a href="https://github.com/styled-components/jest-styled-components#theming" rel="noreferrer">Jest Styled Components documentation</a> I also saw the following solution:</p>
  
  <pre><code>const shallowWithTheme = (tree, theme) =&gt; {
    const context = shallow(&lt;ThemeProvider theme={theme} /&gt;)
      .instance()
      .getChildContext()
    return shallow(tree, { context })
  }
  
  const wrapper = shallowWithTheme(&lt;Button /&gt;, theme)
  </code></pre>
  
  <p>Ok, looks promising. So I added this function to my test file and updated my <code>Check if componentDidUpdate gets called</code> test to this:</p>
  
  <pre><code>test('Check if componentDidUpdate gets called', () =&gt; {
    const spy = jest.spyOn(LooksBrowser.prototype, 'componentDidUpdate');
    const Component = shallowWithTheme(&lt;LooksBrowser currentSlide={{ imageURL: 'http://localhost:3001/img/D1_VW_SPW.jpg', index: 1, subtitle: 'Where amazing happens', title: 'The United States of America', textColor: '#fff' }} nextSlide={{ imageURL: '', index: 0, subtitle: '', title: '', textColor: '' }} /&gt;, Theme);
  
    Component.setProps({ nextSlide: { imageURL: 'http://localhost:3001/img/D2_VW_SPW.jpg', index: 2, subtitle: 'Don\'t walk here at night', title: 'What A View', textColor: '#fff' } });
    expect(spy).toBeCalled();
  });
  </code></pre>
  
  <p>I run the test and get the following error: </p>
  
  <pre><code>Error
  Cannot tween a null target. thrown
  </code></pre>
  
  <p>Makes sense since I'm using <a href="http://airbnb.io/enzyme/docs/api/shallow.html" rel="noreferrer">shallow</a>. So I change the function to use <a href="http://airbnb.io/enzyme/docs/api/ReactWrapper/mount.html" rel="noreferrer">mount</a> instead of shallow:</p>
  
  <pre><code>const shallowWithTheme = (tree, theme) =&gt; {
    const context = mount(&lt;ThemeProvider theme={theme} /&gt;)
      .instance()
      .getChildContext()
    return mount(tree, { context })
  }
  </code></pre>
  
  <p>I run my test again and voila:</p>
  
  <p><code>TypeError: Cannot read property 'SlideTitle' of undefined</code></p>
  
  <p>I'm officially out of ideas. </p>
  
  <p>If anyone has any thoughts on this it would be <em>greatly</em> appreciated! Thanks in advance everyone. </p>
  
  <h2>EDIT</h2>
  
  <p>I've now also opened two issues on Github, one in the <a href="https://github.com/styled-components/styled-components/issues/1471" rel="noreferrer">Styled Components repo</a> and one in the <a href="https://github.com/styled-components/jest-styled-components/issues/119" rel="noreferrer">Jest Styled Components repo</a>.</p>
  
  <p>I've tried all of the solutions provided there so far without any avail. So if anyone here has <strong>any ideas</strong> on how to fix this problem please share them!</p>