Skip to main content

Testing with formatjs

Intl APIs requirements

React Intl uses the built-in Intl APIs in JavaScript. Make sure your environment satisfy the requirements listed in Intl APIs requirements

Mocha

If you're using Mocha as your test runner and testing on older JavaScript runtimes, you can load the Intl Polyfill via the CLI or by adding a <script> in the browser.

Command Line

Run mocha and auto-polyfill the runtime if needed:

$ mocha --recursive test/

Browser

You can either load the polyfill in the browser from node_modules or use the polyfill-fastly.io service from the Financial Times:

<script src="https://polyfill-fastly.io/v2/polyfill.min.js?features=Intl,Intl.~locale.en-US"></script>

Shallow Rendering

React's react-addons-test-utils package contains a shallow rendering feature which you might use to test your app's React components. If a component you're trying to test using ReactShallowRenderer uses React Intl — specifically injectIntl() — you'll need to do extra setup work because React Intl components expect to be nested inside an <IntlProvider>.

Testing Example Components That Use React Intl

The following examples will assume mocha, expect, and expect-jsx test framework.

ShortDate (Basic)

import React from 'react'
import {FormattedDate} from 'react-intl'

const ShortDate = props => (
<FormattedDate
value={props.date}
year="numeric"
month="short"
day="2-digit"
/>
)

export default ShortDate

Testing the <ShortDate> example component is no different than testing any other basic component in your app using React's shallow rendering:

import expect from 'expect'
import expectJSX from 'expect-jsx'
import React from 'react'
import {createRenderer} from 'react-addons-test-utils'
import {FormattedDate} from 'react-intl'
import ShortDate from '../short-date'

expect.extend(expectJSX)

describe('<ShortDate>', function () {
it('renders', function () {
const renderer = createRenderer()
const date = new Date()

renderer.render(<ShortDate date={date} />)
expect(renderer.getRenderOutput()).toEqualJSX(
<FormattedDate value={date} year="numeric" month="short" day="2-digit" />
)
})
})

DOM Rendering

If you use the DOM in your tests, you need to supply the IntlProvider context to your components using composition:

let element = ReactTestUtils.renderIntoDocument(
<IntlProvider>
<MyComponent />
</IntlProvider>
)

However this means that the element reference is now pointing to the IntlProvider instead of your component. To retrieve a reference to your wrapped component, you can use "refs" with these changes to the code:

In your component, remember to add {forwardRef: true} when calling injectIntl():

class MyComponent extends React.Component {
...
myClassFn() { ... }
}
export default injectIntl(MyComponent, {forwardRef: true});

In your test, add a "ref" to extract the reference to your tested component:

const element = React.createRef()
ReactTestUtils.renderIntoDocument(
<IntlProvider>
<MyComponent ref={element} />
</IntlProvider>
)

You can now access the wrapped component instance from element like this:

element.current.myClassFn()

Helper function

Since you will have to do this in all your unit tests, you should probably wrap that setup in a render function like this:

function renderWithIntl(element) {
let instance

ReactTestUtils.renderIntoDocument(
<IntlProvider>
{React.cloneElement(element, {
ref: instance,
})}
</IntlProvider>
)

return instance
}

You can now use this in your tests like this:

const element = React.createRef();
renderWithIntl(<MyElement ref={element}>);
element.current.myClassFn();

@testing-library/react

We recommend using @testing-library/react for testing React components. Below is a helper function to wrap your components with IntlProvider for testing.

Helper function

// test-utils.tsx
import React from 'react'
import {render as rtlRender} from '@testing-library/react'
import {IntlProvider} from 'react-intl'
import * as messages from '../locales/en.json' with {type: 'json'}

interface RenderOptions {
locale?: string
messages?: Record<string, string>
[key: string]: any
}

function render(
ui: React.ReactElement,
{
locale = 'en',
messages: customMessages,
...renderOptions
}: RenderOptions = {}
) {
function Wrapper({children}: {children: React.ReactNode}) {
return (
<IntlProvider locale={locale} messages={customMessages || messages}>
{children}
</IntlProvider>
)
}
return rtlRender(ui, {wrapper: Wrapper, ...renderOptions})
}

// re-export everything
export * from '@testing-library/react'

// override render method
export {render}

Usage

Create a file with the above helper in e.g. test-utils.tsx and import the methods you need in your tests.

import React from 'react'
import {render, screen} from './test-utils'
import {FormattedMessage} from 'react-intl'

const Greeting = ({name}: {name: string}) => (
<div data-testid="greeting">
<FormattedMessage
id="greeting"
defaultMessage="Hello {name}!"
values={{name}}
/>
</div>
)

test('it should render FormattedMessage with the name', () => {
render(<Greeting name="World" />)
expect(screen.getByTestId('greeting')).toHaveTextContent('Hello World!')
})

Jest

Testing with Jest can be divided into two approaches: snapshot's testing and DOM testing. Snapshot's testing is a relatively new feature and works out of the box. If you'd like DOM testing you can use @testing-library/react as shown above or React's TestUtils.

Snapshot Testing

Snapshot testing is a new feature of Jest that automatically generates text snapshots of your components and saves them on the disk so if the UI output changes, you get notified without manually writing any assertions on the component output. Use either helper function or mock as described below.

Helper function

import React from 'react'
import renderer from 'react-test-renderer'
import {IntlProvider} from 'react-intl'

const createComponentWithIntl = (children, props = {locale: 'en'}) => {
return renderer.create(<IntlProvider {...props}>{children}</IntlProvider>)
}

export default createComponentWithIntl

Usage

import React from 'react'
import createComponentWithIntl from '../../utils/createComponentWithIntl'
import AppMain from '../AppMain'

test('app main should be rendered', () => {
const component = createComponentWithIntl(<AppMain />)

let tree = component.toJSON()

expect(tree).toMatchSnapshot()

tree.props.onClick()

tree = component.toJSON()

expect(tree).toMatchSnapshot()
})

You can find runnable example here and more info about Jest here.

DOM Testing

If you want to use Jest with DOM Testing, refer to the @testing-library/react section above or check the official Jest documentation.

Vitest

Vitest is a blazing fast unit test framework powered by Vite. Testing React components with react-intl in Vitest works similarly to Jest, and you can use the same @testing-library/react helper functions.

Setup

First, ensure you have the necessary dependencies:

npm install -D vitest @testing-library/react @testing-library/jest-dom happy-dom

Configuration

Add a vitest.config.ts file to your project:

import {defineConfig} from 'vitest/config'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [react()],
test: {
environment: 'happy-dom',
setupFiles: ['./vitest.setup.ts'],
},
})

Create a vitest.setup.ts file to add global test utilities:

import '@testing-library/jest-dom/vitest'

Testing with Vitest

You can use the same test helper function from the @testing-library/react section. Here's a complete example:

// test-utils.tsx
import React from 'react'
import {render as rtlRender} from '@testing-library/react'
import {IntlProvider} from 'react-intl'
import * as messages from '../locales/en.json' with {type: 'json'}

interface RenderOptions {
locale?: string
messages?: Record<string, string>
[key: string]: any
}

function render(
ui: React.ReactElement,
{
locale = 'en',
messages: customMessages,
...renderOptions
}: RenderOptions = {}
) {
function Wrapper({children}: {children: React.ReactNode}) {
return (
<IntlProvider locale={locale} messages={customMessages || messages}>
{children}
</IntlProvider>
)
}
return rtlRender(ui, {wrapper: Wrapper, ...renderOptions})
}

// re-export everything
export * from '@testing-library/react'

// override render method
export {render}
// MyComponent.test.tsx
import {describe, it, expect} from 'vitest'
import {render, screen} from './test-utils'
import {FormattedMessage, FormattedNumber} from 'react-intl'

const PriceDisplay = ({amount}: {amount: number}) => (
<div>
<FormattedMessage id="price.label" defaultMessage="Price:" />{' '}
<FormattedNumber value={amount} style="currency" currency="USD" />
</div>
)

describe('PriceDisplay', () => {
it('should render price with formatted currency', () => {
render(<PriceDisplay amount={99.99} />)
expect(screen.getByText(/Price:/i)).toBeInTheDocument()
expect(screen.getByText(/\$99\.99/)).toBeInTheDocument()
})

it('should support different locales', () => {
render(<PriceDisplay amount={99.99} />, {locale: 'de-DE'})
// German locale formatting
expect(screen.getByText(/99,99/)).toBeInTheDocument()
})
})

Snapshot Testing with Vitest

Vitest also supports snapshot testing similar to Jest:

import {describe, it} from 'vitest'
import {render} from './test-utils'
import {FormattedDate} from 'react-intl'

const DateDisplay = ({date}: {date: Date}) => (
<FormattedDate value={date} year="numeric" month="long" day="numeric" />
)

describe('DateDisplay', () => {
it('should match snapshot', () => {
const {container} = render(<DateDisplay date={new Date('2024-01-15')} />)
expect(container).toMatchSnapshot()
})
})

Running Tests

Add test scripts to your package.json:

{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}

Then run your tests:

npm run test

For more information, check out the Vitest documentation.

Storybook

Intl

If you want to use react-intl inside of Storybook you can use storybook-addon-intl which provides an easy to use wrapper for react-intl including a locale switcher so you can test your component in all provided languages.