Skip to content

styled-components/jest-styled-components

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

402 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

NPM version CI tested with jest

Jest Styled Components

A set of utilities for testing Styled Components (v5+) with Jest. Provides a snapshot serializer that inlines CSS into snapshots and a toHaveStyleRule matcher for asserting style rules.

Quick Start

npm install --save-dev jest-styled-components
import React from 'react'
import styled from 'styled-components'
import { render } from '@testing-library/react'
import 'jest-styled-components'

const Button = styled.button`
  color: red;
`

test('it works', () => {
  const { container } = render(<Button />)
  expect(container.firstChild).toMatchSnapshot()
  expect(container.firstChild).toHaveStyleRule('color', 'red')
})

To avoid importing in every test file, use the global setup method.

Table of Contents

Snapshot Testing

Without this package, styled-components snapshots contain opaque hashed class names and no CSS rules. Changes to styles only show up as class name diffs, which is uninformative.

After importing jest-styled-components, snapshots include the actual CSS rules and use deterministic class name placeholders (c0, c1, etc.), producing clear diffs πŸ’–:

- Snapshot
+ Received

 .c0 {
-  color: green;
+  color: blue;
 }

 <button
   class="c0"
 />

@testing-library/react

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

test('it works', () => {
  const { container } = render(<Button />)
  expect(container.firstChild).toMatchSnapshot()
})

Snapshots from DOM elements use class instead of className.

react-test-renderer

import renderer from 'react-test-renderer'

test('it works', () => {
  const tree = renderer.create(<Button />).toJSON()
  expect(tree).toMatchSnapshot()
})

Theming

Pass the theme directly as a prop or wrap with ThemeProvider:

import { ThemeProvider } from 'styled-components'

const theme = { main: 'mediumseagreen' }

test('with theme prop', () => {
  const { container } = render(<Button theme={theme} />)
  expect(container.firstChild).toHaveStyleRule('color', 'mediumseagreen')
})

test('with ThemeProvider', () => {
  const { container } = render(
    <ThemeProvider theme={theme}>
      <Button />
    </ThemeProvider>
  )
  expect(container.firstChild).toHaveStyleRule('color', 'mediumseagreen')
})

toHaveStyleRule

Asserts that a CSS property has the expected value. The second argument accepts a string, RegExp, Jest asymmetric matcher, or undefined. When used with .not, the second argument is optional.

const Button = styled.button`
  color: red;
  border: 0.05em solid ${props => props.transparent ? 'transparent' : 'black'};
  cursor: ${props => !props.disabled && 'pointer'};
  opacity: ${props => props.disabled && '.65'};
`

test('default styles', () => {
  const { container } = render(<Button />)
  expect(container.firstChild).toHaveStyleRule('color', 'red')
  expect(container.firstChild).toHaveStyleRule('border', '0.05em solid black')
  expect(container.firstChild).toHaveStyleRule('cursor', 'pointer')
  expect(container.firstChild).not.toHaveStyleRule('opacity')
  expect(container.firstChild).toHaveStyleRule('opacity', undefined)
})

test('prop-dependent styles', () => {
  const { container } = render(<Button disabled transparent />)
  expect(container.firstChild).toHaveStyleRule('border', expect.stringContaining('transparent'))
  expect(container.firstChild).toHaveStyleRule('cursor', undefined)
  expect(container.firstChild).toHaveStyleRule('opacity', '.65')
})

Options

The third argument is an options object for targeting rules within at-rules, with modifiers, or by raw CSS selector.

Option Type Description
container string Match within a @container at-rule, e.g. '(min-width: 400px)'
layer string Match within a @layer at-rule, e.g. 'utilities'
media string Match within a @media at-rule, e.g. '(max-width: 640px)'
supports string Match within a @supports at-rule, e.g. '(display: grid)'
modifier string | css Refine the selector: pseudo-selectors, combinators, & references, or the css helper for component selectors
selector string Match by raw CSS selector instead of component class. Useful for createGlobalStyle

media and modifier

const Button = styled.button`
  @media (max-width: 640px) {
    &:hover {
      color: red;
    }
  }
`

test('media + modifier', () => {
  const { container } = render(<Button />)
  expect(container.firstChild).toHaveStyleRule('color', 'red', {
    media: '(max-width:640px)',
    modifier: ':hover',
  })
})

supports

const Layout = styled.div`
  @supports (display: grid) {
    display: grid;
  }
`

test('supports query', () => {
  const { container } = render(<Layout />)
  expect(container.firstChild).toHaveStyleRule('display', 'grid', {
    supports: '(display:grid)',
  })
})

container

const Card = styled.div`
  container-type: inline-size;
  @container (min-width: 400px) {
    font-size: 1.5rem;
  }
`

test('container query', () => {
  const { container } = render(<Card />)
  expect(container.firstChild).toHaveStyleRule('font-size', '1.5rem', {
    container: '(min-width: 400px)',
  })
})

layer

const Themed = styled.div`
  @layer utilities {
    color: red;
  }
`

test('layer query', () => {
  const { container } = render(<Themed />)
  expect(container.firstChild).toHaveStyleRule('color', 'red', {
    layer: 'utilities',
  })
})

modifier with component selectors

When a rule is nested within another styled-component, use the css helper to target it:

const Button = styled.button`
  color: red;
`

const ButtonList = styled.div`
  ${Button} {
    flex: 1 0 auto;
  }
`

import { css } from 'styled-components'

test('nested component selector', () => {
  const { container } = render(<ButtonList><Button /></ButtonList>)
  expect(container.firstChild).toHaveStyleRule('flex', '1 0 auto', {
    modifier: css`${Button}`,
  })
})

Class name overrides work similarly:

const Button = styled.button`
  background-color: red;
  &.override {
    background-color: blue;
  }
`

test('class override', () => {
  const { container } = render(<Button className="override" />)
  expect(container.firstChild).toHaveStyleRule('background-color', 'blue', {
    modifier: '&.override',
  })
})

selector (createGlobalStyle)

The selector option matches rules by raw CSS selector, bypassing component class name detection. This is the way to test createGlobalStyle:

import { createGlobalStyle } from 'styled-components'

const GlobalStyle = createGlobalStyle`
  body {
    background: white;
  }
`

test('global styles', () => {
  render(<GlobalStyle />)
  expect(document.documentElement).toHaveStyleRule('background', 'white', {
    selector: 'body',
  })
})

When selector is set, the component argument to toHaveStyleRule is ignored -- any rendered element will work as the receiver.

Note on element selection

The matcher checks styles on the root element it receives. To assert on nested elements, query for them first:

const { getByTestId } = render(<MyComponent />)
expect(getByTestId('inner-button')).toHaveStyleRule('color', 'blue')

Vitest

Import the Vitest-specific entry point in your setup file:

import 'jest-styled-components/vitest'

This registers the serializer, matcher, and stylesheet reset using Vitest's expect and beforeEach. TypeScript types are included at jest-styled-components/vitest.

Configure in vitest.config.ts:

export default defineConfig({
  test: {
    setupFiles: ['./src/setupTests.ts'],
  },
})

Bun

Works with Bun's test runner out of the box. Import jest-styled-components in your test setup as usual -- Bun provides the expect and beforeEach globals that the library hooks into.

React Native

For React Native, import the native entry point instead:

import 'jest-styled-components/native'

This registers only the toHaveStyleRule matcher adapted for React Native's style objects (no snapshot serializer needed). It handles style arrays and converts kebab-case properties to camelCase.

import React from 'react'
import styled from 'styled-components/native'
import renderer from 'react-test-renderer'
import 'jest-styled-components/native'

const Label = styled.Text`
  color: green;
`

test('native styles', () => {
  const tree = renderer.create(<Label />).toJSON()
  expect(tree).toHaveStyleRule('color', 'green')
})

Global Setup

To avoid importing in every test file, create a setup file:

// src/setupTests.js
import 'jest-styled-components'

Then add it to your Jest config:

// jest.config.js
module.exports = {
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
}

Serializer

The serializer can be imported separately for use with libraries like jest-specific-snapshot:

import { styleSheetSerializer } from 'jest-styled-components/serializer'
import { addSerializer } from 'jest-specific-snapshot'

addSerializer(styleSheetSerializer)

Serializer Options

import { setStyleSheetSerializerOptions } from 'jest-styled-components/serializer'

setStyleSheetSerializerOptions({
  addStyles: false,                          // omit CSS from snapshots
  classNameFormatter: (index) => `styled${index}`,  // custom class placeholders
})

resetStyleSheet

The main entry point calls resetStyleSheet() in a beforeEach hook automatically. If you use the standalone serializer or a custom test setup where beforeEach is not globally available, call it manually:

import { resetStyleSheet } from 'jest-styled-components'

// In your test setup or beforeEach
resetStyleSheet()

CSS Parse Caching

By default, toHaveStyleRule re-parses the stylesheet on every assertion. For test suites with many assertions, this can be slow. Import the cached entry point to parse once and reuse the result when the stylesheet hasn't changed:

import 'jest-styled-components/cache'

That's itβ€”the cache automatically invalidates when the stylesheet changes (new components render) and when resetStyleSheet runs between tests via beforeEach. No manual cleanup needed.

With the cached entry point, both toHaveStyleRule and the snapshot serializer reuse the parsed stylesheet when possible. The serializer builds a filtered copy of the AST instead of mutating it during serialization.

Legacy: Enzyme

Enzyme is no longer actively maintained. If you still use it, snapshot testing requires enzyme-to-json and toHaveStyleRule works with both shallow and mounted wrappers. Consider migrating to @testing-library/react.

Working with Multiple Packages

If styles are not appearing in snapshots, you may have multiple instances of styled-components loaded (common in monorepos). See the styled-components FAQ: Why am I getting a warning about several instances of module on the page?

Contributing

Open an issue to discuss before submitting a PR.

Contributors

This project exists thanks to all the people who contribute.

Backers

Thank you to all our backers! [Become a backer]

Sponsors

Support this project by becoming a sponsor. [Become a sponsor]

License

Licensed under the MIT License.

About

πŸ”§ πŸ’… Jest utilities for Styled Components

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Packages