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

WRO-438: Update testing page with React Testing Lib #3049

Merged
merged 15 commits into from May 17, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
75 changes: 35 additions & 40 deletions docs/testing-components/test-driven-development/index.md
Expand Up @@ -34,14 +34,12 @@ verify that an IconButton with `minWidth={true}` does not change the child compo
describe('IconButton Specs', () => {

test('should always maintain minWidth=false for its <Button> child', () => {
const iconButton = mount(
<IconButton minWidth>star</IconButton>
);
const button = iconButton.find('Button');
const expected = false;
const actual = (button.prop('minWidth'));

expect(actual).toBe(expected);
render(<IconButton minWidth>star</IconButton>);
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
const button = screen.getByRole('button');

const expected = 'minWidth';

expect(button).toHaveClass(expected);
});
});
```
Expand All @@ -65,14 +63,13 @@ properties are applied to the Button child.

```js
test('should apply same prop to <Button> child', function () {
const iconButton = mount(
render(
<IconButton size="small">star</IconButton>
);
const button = iconButton.find('Button');
const expected = true;
const actual = button.prop('small');

expect(actual).toBe(expected);
const button = screen.getByRole('button');
const expected = 'small'

expect(button).toHaveProperty('size', expected);
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
});
```

Expand All @@ -88,37 +85,35 @@ solution.
We use `Jest` for our unit testing. While there are quite a few comparisons it can help to stick to `.toBe()` and `.not.toBe()`. These methods come after
the `expect()` call.

We use Enzyme to render our components for testing. Enzyme can render a component in one of three different ways. Each
has its benefits and drawbacks. A summary of the methods follows.
We use React Testing Library to render our components for testing.
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved

The primary purpose of React Testing Library is to increase confidence in your tests by testing your components in the way a user would use them. Users don't care what happens behind the scenes, they just see and interact with the output. Instead of accessing the components' internal APIs or evaluating their state, you'll get more confidence by writing your tests based on the component output.

### Shallow Rendering - `shallow()`
### Component Rendering - `render()`

[Shallow](https://github.com/enzymejs/enzyme/blob/master/docs/api/shallow.md) rendering renders the component specified but does not render any of its children. This can be useful when you
only want to test the output of the single object. If you need to be able to test that properties get passed to children,
then you will need to use Mount rendering. Once a component is rendered a number of methods are available to inspect the
output. These include:
The render method works like this:

* `find()` - Returns nodes that match the passed-in selector. For custom components, usually you can use the name of the control
* `contains()` - Returns true if a node or array of nodes exist in the render
* `hasClass()` - Returns true if the component has the specified className
* `children()` - Returns the children of the component, wrapped so that these methods can be applied. (Note: In shallow render, the children will not be complete)
* `props()` - Returns the props of the component
* `prop()` - Returns the value of the specified prop
* `simulate()` - Simulates an event
* `instance()` - Returns the instance of the root component
* `setState()` - Sets the state of the component to the passed-in state
* `setProps()` - Manually sets the props of the component
```js
const {...Results} = render(<Component {...props} />, {...Options});
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
```

### Full Rendering - `mount()`
When we render the component we have a number of render options:

Full rendering or [mount](https://github.com/enzymejs/enzyme/blob/master/docs/api/mount.md) renders the component specified as well as all children. This can be useful when you need to be able to
test that properties get passed to children. Mount rendering uses the same methods as Shallow rendering listed above.
* `{container}` - By default, React Testing Library will create a div and append that div to the document.body and this is where your React component will be rendered. If you provide your own HTMLElement container via this option, it will not be appended to the document.body automatically
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{baseElement}` - If the container is specified, then this defaults to that, otherwise this defaults to document.body. This is used as the base element for the queries as well as what is printed when you use debug()
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{hydrate}` - If hydrate is set to true, then it will render with ReactDOM.hydrate. This may be useful if you are using server-side rendering and use ReactDOM.hydrate to mount your components.
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{legacyRoot}` - Used for apps that requires rendering like in React 17 or older
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{wrapper}` - Pass a React Component as the wrapper option to have it rendered around the inner elementReturns the props of the component
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{queries}` - Queries to bind. Overrides the default set from DOM Testing Library unless merged.

### Static Rendering - `render()`
There are also some render results:

Static rendering or [render](https://github.com/enzymejs/enzyme/blob/master/docs/api/render.md), generates the HTML output from the specified component. Static rendering has several utility methods including:
* `{queries}` - The most important feature of render is that the queries from DOM Testing Library are automatically returned with their first argument bound to the baseElement, which defaults to document.body. See [Queries](https://testing-library.com/docs/queries/about/) for a complete list.
* `{container}` - The containing DOM node of your rendered React Element (rendered using ReactDOM.render)
* `{baseElement}` - If the container is specified, then this defaults to that, otherwise this defaults to document.body. This is used as the base element for the queries as well as what is printed when you use debug()
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{hydrate}` - If hydrate is set to true, then it will render with ReactDOM.hydrate. This may be useful if you are using server-side rendering and use ReactDOM.hydrate to mount your components
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{debug}` - This method is a shortcut for console.log(prettyDOM(baseElement))
* `{rerender}` - This function can be used to update props of the rendered component
* `{unmount}` - This will cause the rendered component to be unmounted
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
* `{asFragment}` - Returns a DocumentFragment of your rendered component

* `text()` - Returns the text of the selected node
* `html()` - Returns the raw HTML of the selected node
* `children()` - Returns the children of the selected node
* `find()` - searches the node for the passed-in selector
70 changes: 30 additions & 40 deletions docs/testing-components/unit-testing/index.md
Expand Up @@ -34,7 +34,7 @@ component or item under test and end with the `"-specs.js"` suffix.
We use a dizzying number of tools to perform unit testing. A quick overview of the different tools can be helpful.

* [Jest](https://jestjs.io/) - A test framework. This tool allows us to setup, assert and run tests. We can also use `jest` as a mocking library.
* [Enzyme](https://enzymejs.github.io/enzyme/) - A test library for use with React. It allows us to shallowly render components and inspect the output.
* [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) - A test library for use with React. It allows us to render components and inspect the output.
* [jsdom](https://github.com/jsdom/jsdom) - A pure-JavaScript implementation of many web standards, notably the WHATWG DOM and HTML Standards, for use with Node.js.

## Unit Testing
Expand Down Expand Up @@ -115,41 +115,29 @@ and outputs we can test basically any JavaScript function that returns a value.

## Testing React

To test react we use [Enzyme](https://enzymejs.github.io/enzyme/) plus other tools you can find out about [here](../test-driven-development/index.md).
To test react we use [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) plus other tools you can find out about [here](../test-driven-development/index.md).

```js
const Text = (props) => {
return <p>{props.content}</p>;
const Text = ({content, ...rest}) => {
return <p {...rest}>{content}</p>;
}

test('Should contain text', () => {
const subject = shallow(
<Text content='sample' />
render(
<Text data-testid="test-text" content='sample' />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, DO NOT USE data-testid when you could use other queries. This is a guidance page so you should not use it at least here.
Please read more at https://testing-library.com/docs/queries/about/#priority and fix this test.

);

const expected = 'sample';
const actual = subject.text()
expect(actual).toBe(expected);
const textElement = screen.getByTestId('test-text');
const expected = 'sample';

expect(textElement).toHaveTextContent(expected);
});
```

If you wish to learn more about Enzyme's library of functions look [here](https://github.com/enzymejs/enzyme).

The three main parts about Enzyme that you need to know are it's rendering methods.

### shallow()

[Shallow](https://github.com/enzymejs/enzyme/blob/master/docs/api/shallow.md) is the virtual DOM representation. It will only render the component plus one level of children. This allows
us to stay within the smaller confines of a component when testing.

### mount()

[mount](https://github.com/enzymejs/enzyme/blob/master/docs/api/mount.md) is the virtual DOM representation. It will render everything inside the component, including all nested children.
This is a little beyond unit testing as you start to test the integration of a few components.
If you wish to learn more about React Testing Library's library of functions look [here](https://testing-library.com/docs/react-testing-library).
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved

alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
### render()

[render](https://github.com/enzymejs/enzyme/blob/master/docs/api/render.md) is the DOM representation. It will print a string of the output dom that the browser sees.
[render](https://testing-library.com/docs/react-testing-library/api#render) is the DOM representation. It will print a string of the output dom that the browser sees.

## Why Unit Testing?

Expand Down Expand Up @@ -194,36 +182,38 @@ This example looks quite silly, but let's look at it in a React context:

```js
//original code
const Text = (props) => {
return <p>{props.content}</p>;
const Text = ({content, ...rest}) => {
return <p {...rest}>{content}</p>;
}

//breaking change
const Text = (props) => {
return <p>{props.cont}</p>;
const Text = ({cont, ...rest}) => {
return <p {...rest}>{cont}</p>;
}
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved

//Example A - Bad
test('Should pass prop to component', () => {
const Text = shallow(
<Text content='sample' />
render(
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
<Text data-testid="test-text" content='sample' />
);


const textElement = screen.getByTestId('test-text');
const expected = 'sample';
const contentProp = Text.prop('content')
expect(contentProp).toBe(expected);

expect(textElement).expect(button).toHaveProperty('content', expected);
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
});

//Example B - Better

test('Should contain text', () => {
const subject = shallow(
<Text content='sample' />
test('Should contain text', () => {
render(
<Text data-testid="test-text" content='sample' />
alexandrumorariu marked this conversation as resolved.
Show resolved Hide resolved
);

const textElement = screen.getByTestId('test-text');
const expected = 'sample';

const expected = 'sample';
const actual = subject.text()
expect(actual).toBe(expected);
expect(textElement).toHaveTextContent(expected);
});
```

Expand Down Expand Up @@ -309,4 +299,4 @@ write tests in those cases.

Please refer to our document [Test Driven Development(TDD)](../test-driven-development/index.md), it contains how to run tests
and how they fit in our testing strategy. It also contains more information about TDD and the methodology behind it. This
document is a deeper dive into the unit test specifically.
document is a deeper dive into the unit test specifically.