diff --git a/generator-eui/component/templates/test.tsx b/generator-eui/component/templates/test.tsx index 34741e18182..b7f6c9471ac 100644 --- a/generator-eui/component/templates/test.tsx +++ b/generator-eui/component/templates/test.tsx @@ -13,12 +13,11 @@ import { requiredProps } from '../../test/required_props'; import { <%= componentName %> } from './<%= fileName %>'; describe('<%= componentName %>', () => { - test('is rendered', () => { - const component = render( + it('renders', () => { + const { container } = render( <<%= componentName %> {...requiredProps} /> ); - expect(component) - .toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); }); diff --git a/src/test/internal/index.ts b/src/test/internal/index.ts index 6f3b672aefe..2d23af02e50 100644 --- a/src/test/internal/index.ts +++ b/src/test/internal/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export * from './render_with_styles'; export * from './render_custom_styles'; export * from './test_custom_hook'; diff --git a/src/test/internal/render_with_styles.ts b/src/test/internal/render_with_styles.ts deleted file mode 100644 index 3e2623da7c6..00000000000 --- a/src/test/internal/render_with_styles.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { render } from 'enzyme'; -import { ReactElement } from 'react'; -import { createSerializer } from '@emotion/jest'; - -// NOTE: This helper currently does not work and should not be called. -// Even when called within an it() block, a stack overflow error occurs. - -/** - * Use this function to add the `@emotion` Jest serializer to a single test. - * The resulting snapshot will include the styles of elements in the component that use `@emotion` for styling. - * - * This function must be run outside of a `test` block: - - ``` - describe('EuiMark', () => { - renderWithStyles(Marked); - - test('is rendered', () => {}); - }); - ``` - */ -export const renderWithStyles = (component: ReactElement) => { - throw new Error( - 'This helper currently does not work and should not be called.' - ); - - afterAll(() => { - expect.addSnapshotSerializer( - createSerializer({ - classNameReplacer(className) { - return className; - }, - }) - ); - expect(render(component)).toMatchSnapshot('renders with emotion styles'); - }); -}; diff --git a/wiki/contributing-to-eui/testing/unit-testing.md b/wiki/contributing-to-eui/testing/unit-testing.md index 2a9f0a20028..3de2f282d2e 100644 --- a/wiki/contributing-to-eui/testing/unit-testing.md +++ b/wiki/contributing-to-eui/testing/unit-testing.md @@ -23,22 +23,25 @@ fully-tested the code is, located at `reports/jest-coverage`. Create test files with the name pattern of `{component name}.test.tsx` in the same directory which contains `{component name}.tsx`. -## Updating snapshots +## Targeting files to test -When you change a component in a way that affects the markup, you will need to update the snapshot in order for the tests to succeed. To do so, run `yarn test-unit -u`. This will update all snapshots in the repo. You can also add any string to the end of the command to run the tests only on directories that contain that string. For example, `yarn test-unit -u button` will only update the tests for directories that **contain** `button`. +You can also add any string to the end of the command to run the tests only on directories that contain that string. For example, `yarn test-unit button` will only update the tests for directories that **contain** `button`. ## Test helpers The [`src/test`](../../../src/test) module exports some functions and constants to help you write better tests: -* `findTestSubject` helps you find DOM nodes in mounted components. * `requiredProps` is a list of all props almost all components should support. -* `takeMountedSnapshot` generates a snapshot of a mounted component. -* `renderWithStyles` generates a snapshot including Emotion style output. +* `shouldRenderCustomStyles` automatically asserts that consumer classNames, Emotion CSS, and custom styles are merged correctly with EUI's styles. +* RTL: + * The exports within `test/rtl` (`render`, `screen`, and `within`) provide out-of-the-box `data-test-subj` querying. `render` provides automatic `EuiProvider` wrapping. +* Enzyme: + * `findTestSubject` helps you find DOM nodes in mounted components. + * `takeMountedSnapshot` generates a snapshot of a mounted component. ### Test helper naming pattern -If the test helper includes `enzyme` or other libraries included only in `devDependencies`, use the `*.test_helper.[ts, tsx]` naming pattern to exclude the component from production builds. +If the test helper includes `enzyme` or other libraries included only in `devDependencies`, use the `*.test_helper.[ts, tsx]` naming pattern to exclude the component from production builds, or place the helper in a namespaced folder. ## Test design @@ -46,8 +49,7 @@ If the test helper includes `enzyme` or other libraries included only in `devDep * DO use the `data-test-subj` attribute to mark parts of a component you want to `find` later. * DON'T depend upon class names or other implementation details for `find`ing nodes, if possible. -* DO use snapshots as much as possible. -* DON'T assert for the presence of nodes if you can use a snapshot instead. +* DON'T use snapshots, except for an initial `it renders` test. Prefer using specific assertions instead. ### Anatomy of a test @@ -59,58 +61,63 @@ A good test will document: * Special behavior, e.g. keyboard navigation, async behavior, DOM manipulation under the hood. ```jsx +import { fireEvent } from '@testing-library/react'; +import { render } from '../../test/rtl'; + describe('YourComponent', () => { - // Snapshot will be generated at the end of the snap file - renderWithStyles( - - Hello - - )); + shouldRenderCustomStyles(); + + it('renders', () => { + const { container } = render( + + ); + + expect(container.firstChild).toMatchSnapshot(); + }); describe('props', () => { - describe('color', () => { - test('is rendered', () => { - const component = render( - - ); + test('color', () => { + const { getByTestSybject } = render( + + ); - expect(component).toMatchSnapshot(); - }); + expect(getByTestSubject('color')).toHaveStyleRule('color', 'blue'); }); describe('onClick', () => { - test(`isn't called upon instantiation`, () => { - const onClickHandler = sinon.stub(); + it('is called when the button is clicked', () => { + const onClickHandler = jest.fn(); - mount( + const { getByTestSubject } = render( ); - expect(onClickHandler).not.toHaveBeenCalled(); + fireEvent.click(getByTestSubject('button')); + + expect(onClickHandler).toHaveBeenCalledTimes(1); }); - test('is called when the button is clicked', () => { - const onClickHandler = sinon.stub(); + it('is not called on keypress', () => { + const onClickHandler = jest.fn(); - const component = mount( + const { getByTestSubject } = render( ); - // NOTE: This is the only way to find this button. - component.find('button').simulate('click'); + fireEvent.keyDown(getByTestSubject('button')); - expect(onClickHandler).toHaveBeenCalledTimes(1); + expect(onClickHandler).not.toHaveBeenCalled(); }); }); }); describe('behavior', () => { - it('button is automatically focused', () => { - const component = mount( + it('automatically focuses button on page load', () => { + const { getByTestSubject } = render( ); - expect(findTestSubject(component, 'button').getDOMNode()).toBe(document.activeElement); + expect(getByTestSubject('button')).toEqual(document.activeElement); }); }); });