# React Test [](https://www.npmjs.com/package/react-test) [](https://github.com/franciscop/react-test/actions) [](https://github.com/franciscop/react-test/blob/master/index.min.js) [](https://github.com/franciscop/react-test/blob/master/package.json)
Expressive testing library for React to make sure your code works as expected:
```js
import $ from "react-test";
it("increments when clicked", async () => {
const counter = $();
expect(counter).toHaveText("0");
await counter.click();
expect(counter).toHaveText("1");
});
```
The `react-test` syntax follows a similar schema to jQuery so it's very easy to write expressive tests. It also adds some Jest and Vite matchers for convenience.
## Getting Started
First you'll need a working React project. As an example you can start a working React project with [Create React App](https://create-react-app.dev/):
```bash
npx create-react-app my-app
cd my-app
```
Then install `react-test`. It is only needed for development:
```bash
npm install react-test --save-dev
```
Finally you can write tests. Let's say you have [the `` component from this example](/tree/master/src/examples/Counter) and you want to test it to make sure it works as expected:
```js
// src/Counter.js
import React, { useState } from "react";
export default function Counter() {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter + 1);
return ;
}
```
```js
// 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 = $();
expect(counter.text()).toBe("0");
});
it("can be incremented with a click", async () => {
const counter = $();
await counter.click();
expect(counter.text()).toBe("1");
});
it("can be incremented multiple times", async () => {
const counter = $();
await counter.click();
await counter.click();
await counter.click();
expect(counter.text()).toBe("3");
});
});
```
Finally run the tests with Jest:
```bash
npm run test
```
### TypeScript
React Test ships with type definitions included — no `@types/` package needed. The custom matchers (`toHaveText`, `toHaveError`, etc.) are automatically added to Jest and Vitest's `expect()`. The `ReactTest` type is exported if you need to annotate variables explicitly:
```ts
import $ from "react-test";
import type { ReactTest } from "react-test";
it("increments when clicked", async () => {
const counter: ReactTest = $();
expect(counter).toHaveText("0");
await counter.click();
expect(counter).toHaveText("1");
});
```
### 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:
```js
import React from "react";
export default function Button({ primary, onClick, children }) {
const background = primary ? "blue" : "gray";
return (
);
}
```
Then we can test it with `react-test` by creating a `Button.test.js` file and adding some assertions:
```js
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 = $();
expect($button).toHaveStyle("background", "gray");
const $primary = $();
expect($primary).toHaveStyle("background", "blue");
});
it("can be clicked", async () => {
const fn = jest.fn();
const $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 = $(
);
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:
```js
import React from "react";
export default function Button({ primary, onClick, children, ...props }) {
const background = primary ? "blue" : "gray";
return (
);
}
```
### Concepts
#### Matched nodes
When we talk about "the first element" or "the elements matched" we always refer to the top-level element (unless specified differently). So in this example:
```js
const list = $(
A
B
);
```
The first element, which is the same as the matched nodes, is the `ul` and **not the
**. We can always "go down a level" with the proper DOM navigation methods:
```js
const list = $(...); // The node
const items = list.children(); // An array of
nodes
```
In this case the _matched nodes_ of `list` is an array containing only the `
`, while the _matched nodes_ for `items` is an array with both of the `
`.
This is very important for many things, e.g. if you are trying to `.filter()` the `
` you need to use `items` and not `list`, same as if you want to get the first `
`'s Node:
```js
list.get(0); //
...
~> The whole thing
items.get(0); //
A
~> The first item
items.get(1); //
B
~> The second item
items.get(-1); //
B
~> The last item
```
`.get()` returns a native DOM Node, which ends the chain. To narrow the matched nodes down to a single one and keep using React Test, use `.eq()`, `.first()` or `.last()`:
```js
items.eq(1); // The
B
item, wrapped
items.first().text(); // "A"
items.last().find("a").click(); // Chaining still works
```
### 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](https://github.com/franciscop/react-test/graphs/contributors) without any involvement of Facebook or [React](https://reactjs.org/).
#### How can I contribute?
Thanks! Please read the [Contributing Guide](./Contributing.md) where we explain how to get started with the project. Right now there are [some beginner-friendly issues](https://github.com/franciscop/react-test/labels/good%20first%20issue) 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](https://github.com/franciscop/react-test/issues/new). 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](https://medium.com/server-for-node-js/getting-a-great-npm-name-b0b2b27a0e1b), but the gist of it is that the npm package was taken [by Deepstream.io](https://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](https://testing-library.com/docs/react-testing-library/intro)?
This is a difficult one. First, React Testing Library, the documentation and the work from [@kentcdodds](https://github.com/kentcdodds) and other collaborators is amazing and I've learned a lot from it. The main differences are:
The syntax follows jQuery-style chaining:
```js
// react-test
import $ from "react-test";
test("Increments when clicked", async () => {
const $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();
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:
```js
import render from "react-test";
test("Increments when clicked", async () => {
const counter = render();
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.
#### When will the 1.0 be ready?
To launch the version 1.0, I'd like to finish a few tasks:
- Write more documentation and normalize it
- Normalize code, specially across testing
- Add some more event-based functionality, like extending native events (if possible).
- Write 5 working examples in total. Counter, Signup, MovieList, CRUD and Swipe (names TBD).
I don't know how long that'll take, right now I'm normalizing the code and documentation.
## Library API
The main export is a function which we call `$` and accepts a React element or fragment:
```js
import $ from "react-test";
const button = $();
expect(button.text()).toBe("Hello world");
```
| DOM navigation | Read data | Events | Others |
| ------------------------ | ------------------ | ---------------------- | -------------------- |
| [.children()](#children) | [.array()](#array) | [.change()](#change) | [.delay()](#delay) |
| [.closest()](#closest) | [.attr()](#attr) | [.click()](#click) | [.props()](#props) |
| [.each()](#each) | [.data()](#data) | [.submit()](#submit) | [.render()](#render) |
| [.eq()](#eq) | [.get()](#get) | [.trigger()](#trigger) | |
| [.filter()](#filter) | [.html()](#html) | [.type()](#type) | |
| [.find()](#find) | [.is()](#is) | | |
| [.first()](#first) | [.text()](#text) | | |
| [.last()](#last) | | | |
| [.not()](#not) | | | |
| [.parent()](#parent) | | | |
| [.siblings()](#siblings) | | | |
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:
```js
const Greeter = () => {
const [name, setName] = useState();
return (
);
};
it("can type in an input", async () => {
const greet = $();
expect(greet.text()).toBe("Hello Anonymous");
await greet.find("input").type("Francisco");
expect(greet.text()).toBe("Hello Francisco");
// ERROR! this or any similar workflow doesn't work as expected!
greet.find("input").get(0).value = "John";
});
```
You can iterate over the matched elements with `for ... of`:
```js
const list = $(
A
B
C
,
);
for (let node of list.children()) {
expect(node.nodeName).toBe("LI");
}
```
### .array()
```js
.array(callback) -> Array
```
Get all of the currently matched nodes as a plain array:
```js
it("can get the text of the children", () => {
const list = $(
A
B
,
);
const texts = list.children().array("textContent");
expect(texts).toEqual(["A", "B"]);
});
```
#### Parameters
`callback`: it can be either of these:
- `Function`: a function that will behave like `.map()`
- `String`: the key to extract the value from each node.
#### Return
A plain array, with the nodes if there's no callback, with the value the callback returns if it's a function or with the values for the given keys passed as a string.
#### Examples
It's very useful to make plain assertions for groups of items:
```js
it("can use a key for each of the nodes", () => {
const list = $(
A
B
,
);
const items = list.children().array("textContent");
expect(items).toEqual(["A", "B"]);
});
```
With a callback you can perform more expressive methods:
```js
it("can use a function to return more complex data", () => {
const list = $(
A
B
,
);
const items = list
.children()
.array((node) => node.nodeName + " " + node.textContent);
expect(items).toEqual(["LI A", "LI B"]);
});
```
### .attr()
```js
.attr(name) -> String|null
```
Read the attribute value of the first node and return its value:
```js
it("can read the different attributes of an input", async () => {
const input = $();
expect(input.attr("name")).toBe("email");
expect(input.attr("value")).toBe("");
expect(input.attr("disabled")).toBe("");
expect(input.attr("placeholder")).toBe(null);
});
```
#### Parameters
`name` (required): the name of the attribute to select.
#### Return
`String|null`: the value of the attribute, or null if the attribute is not set at all.
#### Examples
All the possible returns for different situations:
```js
const input = $();
expect(input.attr("name")).toBe("email");
expect(input.attr("value")).toBe("");
expect(input.attr("disabled")).toBe("");
expect(input.attr("placeholder")).toBe(null);
```
- `input.attr("name")`: returns `"email"`, since it has a key and string value.
- `input.attr("value")`: returns `""`, since the value (defaultValue) is set but empty.
- `input.attr("disabled")`: returns `""`, since a boolean attribute value defaults to an empty string.
- `input.attr("placeholder")`: returns `null`, since the attribute is not set at all.
Find `.find()` to find a specific attribute, use the attribute selector:
```js
const $form = $();
const $firstName = $form.find('[name="firstname"]');
expect($firstName).toHaveValue("");
await $firstName.type("John");
expect($firstName).toHaveValue("John");
```
Check all external links have the `"noopener noreferrer"` value for `rel`:
```js
// Find all of the external links first
const $links = $().find("a[target=_blank]");
// Make sure they follow the schema
for (let link of $links) {
expect($(link).attr("rel")).toBe("noopener noreferrer");
}
```
When [`.toHaveAttribute()`](#tohaveattribute) is available, you can shorten it:
```js
// Find all of the external links first
const $links = $().find("a[target=_blank]");
// Make sure they *all* have rel="noopener noreferrer"
expect($links).toHaveAttribute("rel", "noopener noreferrer");
```
If you are asserting things, you might prefer [`.toHaveAttribute()`](#tohaveattribute) instead of the above:
```js
const $input = $();
expect($input).toHaveAttribute("name", "email");
expect($input).toHaveAttribute("placeholder", "me@example.com");
```
#### Related
- `expect().toHaveAttribute()`: Jest Matcher to check that the element(s) matched have the specified attribuye and/or value.
### .change()
```js
.change(value) -> Promise
```
Trigger a change in all of the matched elements. It should be awaited for the side effects to run and the component to re-rendered:
```js
it("can change the input value", async () => {
const input = $();
expect(input).toHaveValue("hello");
await input.change("world");
expect(input).toHaveValue("world");
});
```
It works on elements of type ``, `