🔝
Powered by Documentation Page

React Test npm install react-test test badge gzip size

Early package! We are looking for beginner Open Source contributors! ❤️

Expressive testing library for React to make sure your code works as expected:

import $ from "react-test";

it("increments when clicked", async () => {
  const counter = $(<Counter />);
  expect(counter.text()).toEqual("0");
  await counter.click();
  expect(counter.text()).toEqual("1");
});

The react-test syntax follows a similar schema to jQuery so it's very easy to write expressive tests. The best way to test declarative code is with an imperative library.

Getting Started

First you'll need a working React project. As an example you can start a working React project with Create React App:

npx create-react-app my-app
cd my-app

Then install react-test. It is only needed for development:

npm install react-test --save-dev

Finally you can write tests. Let's say you have the <Counter /> component from this example and you want to test it to make sure it works as expected:

// src/Counter.test.js
import React from "react";
import $ from "react-test";
import Counter from "./Counter";

describe("Counter.js", () => {
  it("is initialized to 0", () => {
    const counter = $(<Counter />);
    expect(counter.text()).toBe("0");
  });

  it("can be incremented with a click", async () => {
    const counter = $(<Counter />);
    await counter.click();
    expect(counter.text()).toBe("1");
  });

  it("can be incremented multiple times", async () => {
    const counter = $(<Counter />);
    await counter.click();
    await counter.click();
    await counter.click();
    expect(counter.text()).toBe("3");
  });
});

Finally run the tests with Jest:

npm run test

Basics of testing

React applications are divided in components, and these components can be tested either individually or in group. Self-contained components are easier to test, document and debug.

For example, a plain button can be defined with a callback function, and change colors depending on the primary attribute:

import React from "react";

export default function Button ({ primary, onClick, children }) {
  const background = primary ? "blue" : "gray";
  return (
    <button onClick={onClick} style={{ background }}>
      {children}
    </button>
  );
}

Then we can test it with react-test by creating a Button.test.js file and adding some assertions:

import React from "react";
import $ from "react-test";
import Button from "./Button";

describe("Button.js", () => {
  it("has different backgrounds depending on the props", () => {
    const $button = $(<Button>Hello</Button>);
    expect($button).toHaveStyle("background", "gray");
    const $primary = $(<Button primary>Hello</Button>);
    expect($primary).toHaveStyle("background", "blue");
  });

  it("can be clicked", async () => {
    const fn = jest.fn();
    const $button = $(<Button onClick={fn}>Hello</Button>);
    expect(fn).not.toBeCalled();
    await $button.click();
    expect(fn).toBeCalled();
  });

  // FAILS
  it("cannot be clicked if it's disabled", async () => {
    const fn = jest.fn();
    const $button = $(
      <Button onClick={fn} disabled>
        Hello
      </Button>
    );
    await $button.click();
    expect(fn).not.toBeCalled();   // ERROR!
  });
});

Great! All of our tests are working except for the last one. Now we can go back to our component and fix it:

import React from "react";

export default function Button ({ primary, onClick, children, ...props }) {
  const background = primary ? "blue" : "gray";
  return (
    <button onClick={onClick} style={{ background }} {...props}>
      {children}
    </button>
  );
}

FAQ

Is this an official Facebook/React library?

No. This follows the community convention of calling a library related to React as react-NAME. It is made by these contributors without any involvement of Facebook or React.

How can I contribute?

Thanks! Please read the Contributing Guide where we explain how to get started with the project. Right now there are some beginner-friendly issues so please feel free to implement those!

I will try to help as much as possible on the PRs.

I have a problem, how do I fix it?

Don't sweat it, just open an issue. React Test is in an early phase with incomplete documentation so feel free to read the code or ask directly in the issues.

This will change once the library is more stable, there's more documentation and if the community grows (maybe a chat, or reddit group, or ...).

How did you get react-test?

I've written a blog post about this, but the gist of it is that the npm package was taken by Deepstream.io before but not used. So I asked politely and they allowed me to use it.

How is this different from React Testing Library?

This is a difficult one. First, React Testing Library, the documentation and the work from @kentcdodds and other collaborators is amazing and I've learned a lot from it. The main differences are:

The syntax follows jQuery-style chaining:

// react-test
import $ from "react-test";
test("Increments when clicked", async () => {
  const $counter = $(<Counter />);
  expect($counter).toHaveText("0");
  await $counter.click();
  expect($counter).toHaveText("1");
});

// react testing library
import { render, fireEvent } from "@testing-library/react"
test("Increments when clicked", () => {
  const { getByRole, container } = render(<Counter />);
  expect(container).toHaveTextContent("0");
  fireEvent.click(getByRole("button"));
  expect(container).toHaveTextContent("1");
});

React Test is a work in progress, so if you are writing tests for production right now please use one of the better known alternatives.

jQuery syntax, ewwh

That's not really a question! But if for some reason you deeply despise those dollars, perhaps because they remind you of PHP, you can avoid them altogether:

import render from 'react-test';

test("Increments when clicked", async () => {
  const counter = render(<Counter />);
  expect(counter).toHaveText("0");
  await counter.click();
  expect(counter).toHaveText("1");
});

We obviously love React, but let's not forget that jQuery also has some great things as well. This library brings some of these nice things to react testing.

Library API

The main export is a function which we call $ and accepts a React element or fragment:

import $ from 'react-test';
const $button = $(<button>Hello world</button>);
expect($button).toHaveText('Hello world');

Since the API is inspired on jQuery we call React Test $, but you can call it render or anything you prefer.

You cannot modify the DOM directly with this library, but you can trigger events that, depending on your React components, might modify the DOM:

const Greeter = () => {
  const [name, setName] = useState();
  return (
    <div>
      Hello {name || 'Anonymous'}
      <input onChange={e => setName(e.target.value)} />
    </div>
  );
};

// TODO: .type() is not available yet
it('can type', async () => {
  const $greet = $(<Greeter />);
  expect($greet.text()).toBe('Hello Anonymous');
  await $greet.find('input').type('Francisco');
  expect($greet.text()).toBe('Hello Francisco');
});

.attr()

Read the attributes of the first matched element:

.attr("name")

It will read the attributes of the matched elements:

const $input = $(<input name="email" placeholder="me@example.com" />);
expect($input.attr("name")).toBe("email");
expect($input.attr("placeholder")).toBe("me@example.com");

You can select items with a CSS selector that matches attributes:

const $form = $(<LoginForm />);
const $firstName = $form.find('[name="firstname"]');
expect($firstName).toHaveValue('');
await $firstName.type('John');
expect($firstName).toHaveValue('John');

If you are asserting things, you might prefer .toHaveAttribute() instead of the above:

const $input = $(<input name="email" placeholder="me@example.com" />);
expect($input).toHaveAttribute("name", "email");
expect($input).toHaveAttribute("placeholder", "me@example.com");

Example: external links have "noopener noreferrer"

This security feature makes sure no one can hijack your page's data:

// Find all of the external links first
const $links = $(<Page />).find("a[target=_blank]");

// Make sure they follow the schema
for (let link of $links) {
  expect($(link).attr('rel')).toBe("noopener noreferrer");
}

When .toHaveAttribute() is available, you can shorten it:

// Find all of the external links first
const $links = $(<Page />).find("a[target=_blank]");

// Make sure they *all* have rel="noopener noreferrer"
expect($links).toHaveAttribute('rel', 'noopener noreferrer');

.children()

Get the direct children of all of the elements with an optional filter:

.children(filter: string)

Parameters

filter: A selector expression to match elements against

Returns

An instance of react-test with the new children as itst elements.

Usage

Since we return an instance of react-test, we have to use .toArray() to convert it to an array so that we can iterate through them.

import $ from 'react-test'

const List = () => (
  <ul>
    <li>A</li>
    <li>B</li>
  </ul>
);

// Find the text of each element
const text = $(<List />)
  .children()
  .toArray()
  .map(item => $(item).text());

expect(text).toEqual(["A", "B"]);

.click()

.click() -> promise

Simulates a click on all the matched elements. It should be awaited for the side effects to run and the component to re-rendered:

it('clicks the current element', async () => {
  const counter = $(<Counter />);
  expect(counter.text()).toEqual("0");
  await counter.click();
  expect(counter.text()).toEqual("1");
});

.click() already wraps the call with act(), so there's no need for you to also wrap it. Just make sure to await for it.

Parameters

None. Any parameters passed will be ignored.

Returns

A promise that must be awaited before doing any assertion.

Examples

We might want to click a child element and not the top-level one:

it('clicks all buttons inside', async () => {
  const counter = $(<Counter />);
  expect(counter.text()).toEqual("0");
  await counter.find('button').click();
  expect(counter.text()).toEqual("1");
});

Notes

It is internally wrapping the call with act(), so there's no need for you to also wrap it. Just make sure to await for it.

.closest()

Find the first ancestor that matches the selector for each element:

.closest(selector: string)

Parameters

selector: Expression to match elements against

Returns

An instance of react-test with the new elements as nodes

Usage

Since we return an instance of react-test, we have to use .toArray() to convert it to an array so that we can iterate through them.

import $ from 'react-test'

const List = () => (
  <ul>
    <li>
      <a>Hello</a>
    </li>
    <li>
      <a>World</a>
    </li>
  </ul>
);

const names = $(<List />)
  .find("a")
  .closest("li")
  .toArray()
  .map(node => node.nodeName);

expect(names).toEqual(["LI", "LI"]);

.data()

Reads the data-attribute value for the matched element.

.data(name);

Parameters

name: the data-* attribute that we want to get from the first matched element.

Return

A string containing the value stored in the targeted data-* attribute.

Examples

Find the value of the attribute data-id:

const Hello = () => <div data-id="0">Hello World!</div>;
$(<Hello />)
  .first()
  .data("id"); //0

.each()

We are looking for beginner Open Source Contributors to implement this method:

.filter()

We are looking for beginner Open Source Contributors to implement this method:

.find()

Get all of the descendants of the nodes with an optional filter

.find(filter);

Parameters

filter: a string containing a selector that nodes must pass or a function that return a boolean. See .filter() for a better explanation

Return

An instance of React Test with the new children as nodes

Examples

Find all the links in the contact page:

$(<ContactPage />).find('a');

Get the required fields within a submitting form:

u('form').on('submit', function(e){
  var required = u(this).find('[required]');
});

.first()

Retrieve the first of the matched nodes:

.first();
const $list = $(
  <ul>
    <li>Bananas</li>
    <li>Oranges</li>
  </ul>
);

const item = $list.find('li').first();
expect(item.textContent).toBe('Bananas');

Parameters

This method doesn't accept any parameters

Return

The first html node or null if there is none.

Examples

Retrieve the first element of a list:

const $list = $(
  <ul>
    <li>Bananas</li>
    <li>Oranges</li>
  </ul>
);

const item = $list.find('li').first();
expect(item.textContent).toBe('Bananas');

Related

.last() retrieve the last matched element

.html()

Retrieve the html of the elements:

.html()

.is()

Check whether any of the nodes matches the selector:

.is('a')
.is('.active')

.last()

Retrieve the last of the matched nodes

.last();
const $list = $(
  <ul>
    <li>Bananas</li>
    <li>Oranges</li>
  </ul>
);

const item = $list.find('li').last();
expect(item.textContent).toBe('Oranges');

Parameters

This method doesn't accept any parameters

Return

The last html node or null if there is none.

Examples

Retrieve the last element of a list:

const $list = $(
  <ul>
    <li>Bananas</li>
    <li>Oranges</li>
  </ul>
);

const item = $list.find('li').first();
expect(item.textContent).toBe('Oranges');

Related

.first() retrieve the last matched element

.parent()

We are looking for beginner Open Source Contributors to implement this method:

.siblings()

We are looking for beginner Open Source Contributors to implement this method:

.text()

Retrieve the textContent of the elements:

.text()

.toArray()

Get all of the currently matched nodes as a plain array:

const List = () => (
  <ul>
    <li>A</li>
    <li>B</li>
  </ul>
);

// Find the text of each node
const text = $(<List />)
  .children()
  .toArray()
  .map(item => $(item).text());

expect(text).toEqual(["A", "B"]);

.trigger()

Trigger an event. Right now it only accepts 'click'.

.trigger()

.type()

We are looking for beginner Open Source Contributors to implement this method.

Jest Matchers

These helpers are automatically available if you are using jest:

const $button = $(<button className="primary">Click me!</button>);

expect($button).toMatchSelector('button');
expect($button).toHaveClass('primary');
expect($button).toHaveText('Click me!');

This will give much more meaningful errors when failing compared to doing things manually:

// With the Jest Helpers:
expect($button).toHaveClass('secondary');
// Expected <button class="primary"> to include class "secondary"

// Without the helpers:
expect($button.attr('class')).toBe('secondary');
// Expected "primary" to be "secondary"

These expect() matchers work with either a single element or multiple elements, but it's important that you understand the differences in behavior.

When there's a single element in the expect(), then the .not Jest negation makes them a perfect opposite:

// Make sure the button has the class "primary"
expect($button).toHaveClass('primary');

// Make sure the button does NOT have the class "secondary"
expect($button).not.toHaveClass('secondary');

However when there are multiple elements in the expect(), the test follows the English meaning instead of plainly negating the affirmative statement:

// All of them have this class
expect($list.find('li')).toHaveClass('item');

// NONE of the items have the given class
expect($list.find('li')).not.toHaveClass('hidden');

React Test makes Jest's not behave as "NONE" instead of ~not all~, since we found most of the times this is the desired behavior.

.toBeEnabled()

Check whether none of the matched elements have the attribute "disabled":

const $button = $(<button />);
const $input = $(<input disabled />);

expect($button).toBeEnabled();
expect($input).not.toBeEnabled();

For a list of items, it checks whether all of them are enabled or all of them are disabled:

const $form = $(
  <form>
    <input id="banana" />
    <input id="orange" />

    <textarea id="apple" />
    <textarea id="pear" disabled />

    <button id="mango" disabled />
    <button id="coconut" disabled />
  </form>
);

// All of them are enabled
expect($form.find("input")).toBeEnabled();

// All of them are disabled
expect($form.find("button")).not.toBeEnabled();

For the same React code, these do not pass:

// ERROR! Only one of them is enabled
expect($form.find("textarea")).toBeEnabled();
// Expected <textarea id="pear" disabled=""> not to include the attribute "disabled"

// ERROR! At least one of them is enabled
expect($form.find("textarea")).not.toBeEnabled();
// Expected <textarea id="apple"> to include the attribute "disabled"

.toHaveAttribute()

Check whether the matched elements contain the attribute && value

it('has attribute and value', () => {
  const $button = (
    <button type="submit" disabled>
      click
    </button>
  );

  expect($button).toHaveAttribute('type', 'submit');
  expect($button).toHaveAttribute('disabled');
});

It checks whether the matched elements do not contain the attribute

it('does not have the attribute and value', () => {
  const $button = (
    <button type="submit" disabled>
      click
    </button>
  );

  expect($button).not.toHaveAttribute('onclick');
  expect($button).not.toHaveAttribute('type', 'reset');
});

It checks whether the matched elements contain the attribute and the matched regex value

it('checks if attribute has given regex value', () => {
  const $button = (
    <button type="submit" disabled>
      click
    </button>
  );

  // Positive assertions: all the given regex values match
  expect($button).toHaveAttribute('type', /submit/);
  expect($button).toHaveAttribute('type', /su?b.+/);
  expect($button).toHaveAttribute('type', /.*/);

  // Negative assertions: all the given regex values do not match
  expect($button).not.toHaveAttribute('type', /sub/);
  expect($button).not.toHaveAttribute('type', /su?b/);
  expect($button).not.toHaveAttribute('type', /.*q/);
});

For a list of items, it checks whether all have the same attribute && value or regex

const $list = $(
  <ul>
    <li value="1" title="list-item">
      apple
    </li>
    <li value="2" title="list-item">
      apple
    </li>
  </ul>
);

// PASS
expect($list.find('li')).toHaveAttribute('value');
expect($list.find('li')).toHaveAttribute('title', 'list-item');
expect($list.find('li')).toHaveAttribute('title', /list-item/);
expect($list.find('li')).toHaveAttribute('title', /^li.t-.*/);

// DO NOT PASS
expect($list.find('li')).toHaveAttribute('error');
expect($list.find('li')).toHaveAttribute('id');
expect($list.find('li')).toHaveAttribute('value', '1');
expect($list.find('li')).toHaveAttribute('title', /list/);

For a list of items, it checks whether any do not have the same attribute && value or regex

const $list = $(
  <ul>
    <li id="first" value="1" title="list-item">
      apple
    </li>
    <li value="2" title="list-item">
      apple
    </li>
  </ul>
);

// PASS
expect($list.find('li')).not.toHaveAttribute('error');
expect($list.find('li')).not.toHaveAttribute('value', '1');
expect($list.find('li')).not.toHaveAttribute('title', /list/);

// DO NOT PASS
expect($list.find('li')).not.toHaveAttribute('value');
expect($list.find('li')).not.toHaveAttribute('value', '1');
expect($list.find('li')).not.toHaveAttribute('title', /^list-.*/);

.toHaveClass()

Check whether all of the matched elements have the expected class name:

const $button = $(
  <button className="primary">
    Click me
  </button>
);

expect($button).toHaveClass('primary');
expect($button).not.toHaveClass('secondary');

For list of items, it checks whether all of them match or none of them match:

const $list = $(
  <ul>
    <li className="item main">a</li>
    <li className="item secondary">b</li>
  </ul>
);

// All of them have the class item
expect($list.find('li')).toHaveClass('item');

// None of them has the class "primary"
expect($list.find('li')).not.toHaveClass('primary');

For the same React code, these do not pass:

// ERROR! Only one of them has the class "main"
expect($list.find('li')).toHaveClass('main');
// Expected <li class="item secondary"> to include class "main"

// ERROR! At least one of them has the class "main"
expect($list.find('li')).not.toHaveClass('main');
// Expected <li class="item main"> not to include class "main"

.toHaveHtml()

Checks whether the selected elements have HTML

const $div = $(
  <div>
    <span>I am a span</span>
  </div>
);

expect($div).toHaveHtml('<span>I am a span</span>');

Checks whether the selected elements do not have HTML

const $div = $(
  <div>
    <span>I am a span</span>
  </div>
);

expect($div).not.toHaveHtml('<li>I am a list item</li>');

Trims passed HTML

const $div = $(
  <div>
    <span>I am a span</span>
  </div>
);

expect($div).toHaveHtml('<span>I am a span</span>        ');

Validates across different depth levels of inner HTML

const $div = $(
  <div>
    <span>
      I am a <b>span</b>
    </span>
  </div>
);

expect($div).toHaveHtml('<div><span>I am a <b>span</b></span></div>');
expect($div).toHaveHtml('<span>I am a <b>span</b></span>');
expect($div).toHaveHtml('<b>span</b>');

For a list of elements, checks if all the elements have HTML

const $body = $(
  <body>
    <div>
      <span>span text</span>
    </div>
    <div>
      <span>span text</span>
    </div>
  </body>
);

// PASS
expect($body.find('div')).toHaveHtml('<span>span text<span>');
expect($body.find('div')).toHaveHtml('span text');

// DO NOT PASS
expect($body.find('div')).toHaveHtml('<li>item</li>');
expect($body.find('div')).toHaveHtml('<p>text</p>');

For a list of elements, checks if any of the elements do not have HTML

const $body = $(
  <body>
    <div>
      <span>text</span>
    </div>
    <div>
      <p>text</p>
    </div>
  </body>
);

// PASS
expect($body.find('div')).not.toHaveHtml('<h1>header</h1>');
expect($body.find('div')).not.toHaveHtml('<li>item</li>');
expect($body.find('div')).not.toHaveHtml('<span>random text</span>');

// DO NOT PASS
expect($body.find('div')).not.toHaveHtml('<span>text</span>');
expect($body.find('div')).not.toHaveHtml('<p>text</p>');
expect($body.find('div')).not.toHaveHtml('text');

.toHaveStyle()

Check whether all of the matched elements have the expected styles applied:

const $button = $(<button></button>);

// Check for presence of styles using style object as an argument
expect($button).toHaveStyle({ backgroundColor: "red", textAlign: "center" });
expect($button).toHaveStyle({ textAlign: "center" });

// Check for presence of styles using style string as an argument
expect($button).toHaveStyle("background-color: red; text-align: center;");
expect($button).toHaveStyle("text-align: center;");

// Check for absence of particular styles
expect($button).not.toHaveStyle("display: none;");
expect($button).not.toHaveStyle({ display: "none" });
const $list = $(
  <ul>
    <li style={{ ...styleObj, color: "red" }}></li>
    <li style={{ ...styleObj, color: "red" }}></li>
  </ul>
);

// All of the matching elements have the searched for styles
expect($list.find("li")).toHaveStyle({ color: "red" });
expect($list.find("li")).toHaveStyle("color: red");

// None of the matching elements have the searched for styles
expect($list.find("li")).not.toHaveStyle({ color: "green" });
expect($list.find("li")).not.toHaveStyle("color: green");

.toHaveText()

Check whether the matched elements all contain the text (see the Counter example):

it('can be clicked', async () => {
  const $counter = <Counter />;
  expect($counter).toHaveText('0');
  await $counter.click();
  expect($counter).toHaveText('1');
});

It normalizes whitespace so that multiple spaces or enters are collapsed into a single one:

it('normalizes whitespace', () => {
  const $text = $(
    <div>
      Hello <br /> world!
    </div>
  );
  expect($text).toHaveText('Hello world!');
});

For list of items, it checks whether all of them match or none of them match:

const $list = $(
  <ul>
    <li>apple</li>
    <li>apple</li>
  </ul>
);
// Passes; all of them have the given text
expect($list.find('li')).toHaveText('apple');

// Passes; none of them has the given text
expect($list.find('li')).not.toHaveText('banana');

These examples do not pass:

const $list = $(
  <ul>
    <li>apple</li>
    <li>banana</li>
  </ul>
);

// ERROR! Because only one of them has the text "apple"
expect($list.find('li')).toHaveText('apple');
// Expected <li> to have text "apple" but it received "banana"

// ERROR! Because at least one of them has the text "apple"
expect($list.find('li')).not.toHaveText('apple');
// Expected <li> not to have the text "apple"

.toHaveValue()

  • Checks whether the element has the given value.
  • Only works for input, textarea, and select tags.
  • For input types of checkbox and radio, please use .checked instead.

Checks whether the form element has the given value:

const $input = $(<input type="text" value="textValue" onChange={} />);
expect($input).toHaveValue('textValue');

Checks defaultValue if set on element:

const $input = $(<input type="text" defaultValue="initial text" />);
const $textarea = $(<textarea defaultValue="initial textarea" />);

expect($input).toHaveValue('initial text');
expect($textarea).toHaveValue('initial textarea');

It only works on the input, textarea, and select tags:

const $textInput = $(<input type="text" value="text" onChange={} />);
const $numberInput = $(<input type="number" value="10" onChange={} />);
const $textarea = $(<textarea value="text description" onChange={} />);
const $select = $(
  <select value="second" onChange={}>
    <option value="first">first</option>
    <option value="second">second</option>
    <option value="third">third</option>
  </select>
);

// POSITIVE ASSERTIONS
expect($textInput).toHaveValue('text');
expect($numberInput).toHaveValue(10);
expect($textarea).toHaveValue('text description');
expect($select).toHaveValue('second');

// NEGATIVE ASSERTIONS
expect($textInput).not.toHaveValue(10);
expect($numberInput).not.toHaveValue('text');
expect($textarea).not.toHaveValue('random');
expect($select).not.toHaveValue('first');

Please use .checked for inputs of type checkbox and radio:

const $checkbox = $(<input type="checkbox" checked readOnly />);
const $radio = $(<input type="radio" value="something" checked readOnly />);

// ERROR: Cannot check .toHaveValue() for input type="checkbox" or type="radio".
expect($checkbox).toHaveValue('check');
expect($radio).toHaveValue('radio');

Element that don't contain the value attribute will throw errors:

const $button = $(<button>click</button>);
const $link = $(<a href="hello.com">click</a>);

// ERROR: 'Not a valid element that has a value attribute. Please insert an element that has a value.'
expect($button).toHaveValue('button');
expect($link).toHaveValue('link');

.toMatchSelector()

Checks whether the matched elements match the selector

const $button = $(
  <button id="the-button" className="a-button">
    click
  </button>
);

expect($button).toMatchSelector('#the-button');
expect($button).toMatchSelector('.a-button');

Checks whether the matched elements do not match the selector

const $button = $(
  <button id="the-button" className="a-button">
    click
  </button>
);

expect($button).not.toMatchSelector('#hello');
expect($button).not.toMatchSelector('.world');

For a list of items, it checks if all the elements match the provided selector

const $list = $(
  <ul>
    <li id="first-list-item" className="list-item">
      apple
    </li>
    <li className="list-item">apple</li>
  </ul>
);

// PASS
expect($list.find('li')).toMatchSelector('li');
expect($list.find('li')).toMatchSelector('.list-item');

// DO NOT PASS
expect($list.find('li')).toMatchSelector('.item');
expect($list.find('li')).toMatchSelector('#first-list-item');

For a list of items, it checks if any of the elements do not match the provided selector

const $list = $(
  <ul>
    <li id="first-list-item" className="list-item">
      apple
    </li>
    <li className="list-item">apple</li>
  </ul>
);

// PASS
expect($list.find('li')).not.toMatchSelector('div');
expect($list.find('li')).not.toMatchSelector('.hello');
expect($list.find('li')).not.toMatchSelector('#first-list-item');

// DO NOT PASS
expect($list.find('li')).not.toMatchSelector('li');
expect($list.find('li')).not.toMatchSelector('.list-item');

Examples

These examples define some component or components, and then explain how to test them with react-test. The idea is to have a variety of examples so that some of them will be similar to your code.

These are the examples we want to have:

  • <Counter />: increments when you click it.
  • <Todo />: can add items with React's useState().
  • <Subscribe />: to see how to test API calls.
  • <Portfolio />: with React Router to see how routing works.
  • Redux example: to see the possibilities of integration with Redux. Use one from the official docs.

<Counter />

Let's say that we have a simple counter. Every time you click it, its value increments by one:

// Counter.js
import React, { useState } from "react";

export default function Counter () {
  const [counter, setCounter] = useState(0);
  const increment = () => setCounter(counter + 1);
  return <button onClick={increment}>{counter}</button>;
};

All of our tests must be wrapped by describe() and need to import at least React, react-test and the component that we want to test:

// Counter.test.js
import React from "react";
import $ from "react-test";
import Counter from "./Counter";

describe("Counter.js", () => {

  // Write your tests here
  // All of the examples below should go here

});

First, we might want to check the initial value of this counter. We will render it and use .toHaveText() to check the plain-text content. Please note how this is text "0" and not a number ~0~:

it("starts with 0", () => {
  const counter = $(<Counter />);
  expect(counter).toHaveText("0");
});

Great, this passes our test.

Now let's try clicking it once. Any user action must be treated as asynchronous, so we create a new async test for this.

We are also going to be using the .click() method and awaiting for this click to be resolved:

it("increments when clicked", async () => {
  const counter = $(<Counter />);
  await counter.click();
  expect(counter).toHaveText("1");
});

Let's repeat this with multiple clicks as well:

it("can be incremented multiple times", async () => {
  const counter = $(<Counter />);
  await counter.click();
  await counter.click();
  await counter.click();
  expect(counter).toHaveText("3");
});

This component is working; the user clicks it, the value increments. That's awesome and most of the cases we would be done 🎉

But I also want to make sure there's not an issue where the state is shared. While in here the implementation is trivial, but in some cases it's not. So let's create two components and only one of them:

it("remains independent of other components", async () => {
  const counter1 = $(<Counter />);
  const counter2 = $(<Counter />);
  await counter2.click();
  expect(counter1).toHaveText("0");
  expect(counter2).toHaveText("1");
});

These also remain independent, great! I am sure this simple counter is working as expected. Let's see it all put together:

// Counter.test.js
import React from "react";
import $ from "react-test";
import Counter from "./Counter";

describe("Counter.js", () => {
  it("starts with 0", () => {
    const counter = $(<Counter />);
    expect(counter).toHaveText("0");
  });

  it("increments when clicked", async () => {
    const counter = $(<Counter />);
    await counter.click();
    expect(counter).toHaveText("1");
  });

  it("can be incremented multiple times", async () => {
    const counter = $(<Counter />);
    await counter.click();
    await counter.click();
    await counter.click();
    expect(counter).toHaveText("3");
  });

  it("remains independent of other components", async () => {
    const counter1 = $(<Counter />);
    const counter2 = $(<Counter />);
    await counter2.click();
    expect(counter1).toHaveText("0");
    expect(counter2).toHaveText("1");
  });
});