🔝

React Test npm install react-test test badge gzip size

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).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 matchers (if you are using Jest) for convenience.

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

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.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>;
}
// 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>
  );
}

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:

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

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:

    const list = $(...);  // The node <ul>
    const items = list.children();  // An array of <li> nodes
    

    In this case the matched nodes of list is an array containing only the <ul>, while the matched nodes for items is an array with both of the <li>.

    This is very important for many things, e.g. if you are trying to .filter() the <li> you need to use items and not list, same as if you want to get the first <li>'s Node:

    list.get(0); // <ul>...</ul> ~> The whole thing
    items.get(0); // <li>A</li>   ~> The first item
    items.get(1); // <li>B</li>   ~> The second item
    items.get(-1); // <li>B</li>   ~> The last item
    

    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.

    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:

    import $ from "react-test";
    const button = $(<button>Hello world</button>);
    expect(button.text()).toBe("Hello world");
    
    DOM navigation Read data Promises (Events+timing)
    .children() .array() .change()
    .closest() .attr() .click()
    .each() .data() .delay()
    .filter() .get() .submit()
    .find() .html() .trigger()
    .not() .is() .type()
    .parent() .text()
    .siblings()

    Others: .props() and .render().

    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>
      );
    };
    
    it("can type in an input", async () => {
      const greet = $(<Greeter />);
      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:

    const list = $(
      <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
      </ul>
    );
    
    for (let node of list.children()) {
      expect(node.nodeName).toBe("LI");
    }
    

    .array()

    .array(callback) -> Array
    

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

    it("can get the text of the children", () => {
      const list = $(
        <ul>
          <li>A</li>
          <li>B</li>
        </ul>
      );
      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:

    it("can use a key for each of the nodes", () => {
      const list = $(
        <ul>
          <li>A</li>
          <li>B</li>
        </ul>
      );
      const items = list.children().array("textContent");
      expect(items).toEqual(["A", "B"]);
    });
    

    With a callback you can perform more expressive methods:

    it("can use a function to return more complex data", () => {
      const list = $(
        <ul>
          <li>A</li>
          <li>B</li>
        </ul>
      );
      const items = list
        .children()
        .array((node) => node.nodeName + " " + node.textContent);
      expect(items).toEqual(["LI A", "LI B"]);
    });
    

    .attr()

    .attr(name) -> String|null
    

    Read the attribute value of the first node and return its value:

    it("can read the different attributes of an input", async () => {
      const input = $(<input name="email" defaultValue="" disabled />);
      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:

    const input = $(<input name="email" defaultValue="" disabled />);
    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:

    const $form = $(<LoginForm />);
    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:

    // 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");
    

    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");
    

    Related

    • expect().toHaveAttribute(): Jest Matcher to check that the element(s) matched have the specified attribuye and/or value.

    .change()

    .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:

    it("can change the input value", async () => {
      const input = $(<input defaultValue="hello" />);
      expect(input).toHaveValue("hello");
      await input.change("world");
      expect(input).toHaveValue("world");
    });
    

    It works on elements of type <input>, <textarea> and <select>.

    .change() already wraps the call with act(), so there's no need for you to also wrap it.

    Parameters

    value: the new value for the element. If it's a text input, textarea or select, it should be a string. If it's a checkbox or radio, it should be a true/false boolean.

    Return

    A promise that must be awaited before doing any assertion.

    Examples

    Simple way to test that the input text can be changed:

    it("works with inputs", async () => {
      const input = $(<input defaultValue="hello" />);
      expect(input).toHaveValue("hello");
      await input.change("Francisco");
      expect(input).toHaveValue("Francisco");
    });
    

    For checkboxes it should receive a true/false:

    it("works with inputs", async () => {
      const input = $(<input type="checkbox" />);
      expect(input.get(0).checked).toBe(false);
      await input.change(true);
      expect(input.get(0).checked).toBe(true);
    });
    

    Notes

    Expect this component to change in the future, since its behavior now is complex and inconsistent. So in the future we will do either of these:

    • Make it more complex AND consistent, e.g. accept numbers for <input type="number" />, a text option for type="radio" (with validation), etc.
    • Split into different methods, each one being simpler, e.g. .text(newValue) for text inputs, .check, .check(false) or .uncheck() for checkboxes, .pick(opt) for selects, etc.
    • Other?

    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.

    .children()

    .children(selector) -> $
    

    Get the children nodes of all of the matched elements, optionally filtering them with a CSS selector:

    it("can select all list items", async () => {
      const list = $(
        <ul>
          <li>A</li>
          <li>B</li>
        </ul>
      );
      expect(list.children().text()).toBe("A");
      expect(list.children(":last-child").text()).toBe("B");
    });
    

    Parameters

    selector: A CSS selector expression to match elements against

    Return

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

    Examples

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

    it("can get the children", () => {
      const List = () => (
        <ul>
          <li>A</li>
          <li>B</li>
        </ul>
      );
    
      // Find the text of each element
      const text = $(<List />)
        .children()
        .array((item) => item.textContent);
    
      expect(text).toEqual(["A", "B"]);
    });
    

    .click()

    .click() -> Promise
    

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

    it("can click the counter", 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.

    Parameters

    None. Any parameters passed will be ignored.

    Return

    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");
    });
    

    We can submit a form by clicking on a button inside it:

    const CreateUser = ({ onSubmit }) => (
      <form
        onSubmit={(e) => {
          e.preventDefault(); // <- this is required _when testing_
          onSubmit();
        }}
      >
        <input name="firstname" />
        <input name="lastname" />
        <input name="age" />
        <button>Submit</button>
      </form>
    );
    
    it("submits the form when clicking the button", async () => {
      const onSubmit = jest.fn();
      const createUser = $(<CreateUser onSubmit={onSubmit} />);
      expect(onSubmit).not.toBeCalled();
      await createUser.find("button").click();
      expect(onSubmit).toBeCalled();
    });
    

    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()

    .closest(selector) -> $
    

    Find the first ancestor that matches the selector for each element (deduplicated):

    it("finds all the list items with a link", async () => {
      const list = $(
        <ul>
          <li>
            <a>A</a>
          </li>
          <li>B</li>
        </ul>
      );
      const item = list.find("a").closest("li");
      expect(item.text()).toBe("A");
      expect(item.html()).toBe("<li><a>A</a></li>");
    });
    

    Parameters

    selector: Expression to match elements against

    Return

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

    Usage

    Since we return an instance of react-test, we have to use .array() 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")
      .array()
      .map((node) => node.nodeName);
    
    expect(names).toEqual(["LI", "LI"]);
    

    .data()

    .data(name) -> String|null
    

    Read the data-attribute value of the first node and return its value:

    it("can read the data attributes", () => {
      const card = $(
        <div data-id="25" data-selected>
          Card
        </div>
      );
      expect(card.data("id")).toBe("25");
      expect(card.data("selected")).toBe("true"); // T_T 🤷‍♂️ gh/facebook/react/24812
      expect(card.data("name")).toBe(null);
    });
    

    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>);
    expext(hello.data("id")).toBe("0"); //0
    

    .delay()

    .delay(time) -> Promise
    

    Makes the component to wait for the specified period of time in milliseconds:

    it("can wait for an async action", async () => {
      const down = $(<CountDown />);
      expect(down.text()).toBe("3");
      await down.delay(4000); // 4 seconds
      expect(down.text()).toBe("Done!");
    });
    

    .delay() already wraps the call with act(), so there's no need for you to also wrap it.

    Parameters

    time: the amount of time the component will wait in milliseconds.

    Return

    A plain promise that needs to be awaited.

    Examples

    A component that changes after 1 second:

    const Updater = () => {
      const [text, setText] = useState("initial");
      useEffect(() => {
        setTimeout(() => setText("updated"), 1000);
      }, []);
      return <div>{text}</div>;
    };
    

    For testing, we check the initial value and the value after 2 seconds:

    const updater = $(<Updater />);
    expect(updater.text()).toBe("initial");
    await updater.delay(2000);
    expect(updater.text()).toBe("updated");
    

    .each()

    .each(callback) -> $
    

    Iterates over each of the nodes and returns the same collection of nodes as there was before:

    it("can iterate over the items", () => {
      const list = $(
        <ul>
          <li>A</li>
          <li>B</li>
          <li>C</li>
        </ul>
      );
      const texts = [];
      const out = list.find("li").each((node) => texts.push(node.textContent));
      expect(texts).toEqual(["A", "B", "C"]);
      expect(out.get().textContent).toBe("A");
    });
    

    Parameters

    callback: the function that receives each of the nodes currently matched. It receives (similarly to JS' .forEach()):

    • node: the current node being iterated on.
    • index: the index of the current node in the matched list.
    • list: an array with all of the nodes that are being iterated over.

    Return

    An instance of React-Test with the same collection as before calling .each().

    .filter()

    .filter(selector) -> $
    

    Keep only the nodes that match the selector, removing the others:

    it("can get just the users", () => {
      const list = $(
        <ul>
          <li className="user">John</li>
          <li className="group">Ibiza</li>
          <li className="user">Sarah</li>
        </ul>
      );
      const people = list.children().filter(".user");
      expect(people.array((node) => node.textContent)).toEqual(["John", "Sarah"]);
    });
    

    Parameters

    selector: any one of these:

    • a string containing the CSS selector that nodes must match
    • a ReactTest instance containing a number of nodes. All the matched nodes must be in the ReactTest instance nodes
    • a callback that will keep the element if it returns true. It receives:
      • node: the current node being iterated on.
      • index: the index of the current node in the matched list.
      • list: an array with all of the nodes that are being iterated over.

    Return

    An instance of React Test with only the matching nodes.

    Examples

    Filter to select list items with child links from the contact page:

    $(<ContactPage />)
      .find("a")
      .parent()
      .filter("li");
    

    Related

    • .not(selector): a method that returns a new collection only with nodes that pass the matcher.

    .find()

    .find(selector) -> $
    

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

    Parameters

    selector: a string containing a selector that nodes must pass or a function that return a boolean. See .filter() for a complete explanation of how selectors work.

    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()

    .get(index) -> NodeElement
    

    Get a native DOM Node given its index. Defaults to the first element:

    const list = $(
      <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
        <li>D</li>
      </ul>
    );
    expect(list.find("li").get()).toHaveText("A");
    expect(list.find("li").get(1)).toHaveText("B");
    expect(list.find("li").get(-1)).toHaveText("D");
    

    Parameters

    index: the index of the element to get. It defaults to 0, and you can also set a negative indexes. Any overflowing index will be wrapped around. If there's only a single top-level element, that will be returned.

    Return

    A single NodeElement representing the content that was matched by the index.

    Notes

    This is very useful if you want to use the browser DOM API for testing different properties, like:

    • <input type="checkbox" /> can be tested with expect(input.get().checked).toBe(true);
    • <input required ... /> can be tested with expect(input.get().valid).toBe(true);
    • Type of HTML element can be tested with expect(component.get().nodeName).toBe("FORM")
    • etc.

    Basically any time that you are would like to fall back to the native DOM API for testing, you can use .get()..

    Technically if you want to wrap it again, you can simply do $(form.get()), though we wouldn't recommend to use this too much and see the ReactTest instance => Node as one way operation.

    Examples

    Get the form element to make assertions with FormData:

    const form = $(<SignupForm />);
    const data = new FormData(form.get());
    expect(data.get("firstname")).toBe("");
    

    Return a nested element of a list:

    const list = $(
      <ul>
        <li>A</li>
        <li>B</li>
      </ul>
    );
    const first = list.get(0);
    expect(first.textContent).toBe("A");
    

    Related

    • .array(): get ALL of the current nodes as a plain array.

    .html()

    .html() -> String
    

    Retrieve the OuterHTML of the first element matched:

    it("can extract the plain html", () => {
      const card = $(<div className="card">Hello</div>);
      expect(card.html()).toBe(`<div class="card">Hello</div>`);
    });
    

    Parameters

    None

    Return

    A String with the HTML of the first element.

    .is()

    .is(selector) -> Boolean
    

    Check whether all of the nodes match the selector:

    it("can properly match the button", () => {
      const button = $(<a className="button active">Click me</a>);
    
      expect(button.is("a")).toBe(true);
      expect(button.is(".active")).toBe(true);
      expect(button.is(".inactive")).not.toBe(true);
    });
    

    Parameters

    selector: any one of these:

    • a string containing the CSS selector that nodes must match
    • a ReactTest instance containing a number of nodes. All the matched nodes must be in the ReactTest instance nodes
    • a callback that will keep the element if it returns true. It receives:
      • node: the current node being iterated on.
      • index: the index of the current node in the matched list.
      • list: an array with all of the nodes that are being iterated over.

    Return

    A boolean, true to indicate all of the selector matches all of the nodes, false to indicate at least one (or more) fail the condition. An empty array will always return true.

    Notes

    For a given selector, if you apply .filter(selector).is(selector) (both being the same CSS selector) it will always return true.

    Examples

    TODO

    // Check that the important class belongs to a direct list item
    expect(list.find(".important").is(list.children())).toBe(true);
    

    Related

    • .filter(selector): a method that returns a new collection only with nodes that pass the matcher.

    .map()

    .map(callback) -> $
    

    Iterates over each of the nodes and returns a new collection with the nodes that were returned from the callback:

    it("can get a new collection", () => {
      const list = $(
        <ul>
          <li>A</li>
          <li>B</li>
        </ul>
      );
      // Same as .find('li')
      const items = list.map((node) => node.querySelectorAll("li"));
      expect(items.array((node) => node.nodeName)).toEqual(["LI", "LI"]);
    });
    

    Parameters

    callback: the function that receives each of the nodes currently matched. It receives (similarly to JS' .forEach()):

    • node: the current node being iterated on.
    • index: the index of the current node in the matched list.
    • list: an array with all of the nodes that are being iterated over.
    • returns the new nodes

    Return

    An instance of React-Test with the new collection of nodes. Nested arrays (or NodeLists) are flattened and empty and duplicates items are also removed.

    Related

    • .each(callback): Similar to .map(), but returns the original collection.

    .not()

    .not(selector) -> $
    

    Remove the matched nodes from the collection. It's the opposite of .filter():

    it("can get just the users", () => {
      const list = $(
        <ul>
          <li className="user">John</li>
          <li className="group">Ibiza</li>
          <li className="user">Sarah</li>
        </ul>
      );
      const people = list.children().not(".group");
      expect(people.array((node) => node.textContent)).toEqual(["John", "Sarah"]);
    });
    

    Parameters

    selector: any one of these:

    • a string containing the CSS selector that nodes must not match
    • a ReactTest instance containing a number of nodes. All the matched nodes must not be in the ReactTest instance nodes

    A callback is not allowed, instead use .filter(node => true|false) and negate the condition for better testing clarity.

    Return

    An instance of React Test with only the nodes that did not match the selector.

    Examples

    Get a list of non-important items:

    it("can get all non-important list items", () => {
      const List = () => (
        <ul>
          <li>A</li>
          <li className="important">B</li>
          <li>C</li>
        </ul>
      );
    
      // Find the text of each element
      const text = $(<List />)
        .children()
        .not(".important")
        .array((item) => item.textContent);
    
      expect(text).toEqual(["A", "C"]);
    });
    

    Get all the rows except the headers (first row):

    it("can get all children except the first", () => {
      const Table = () => (
        <table>
          <tbody>
            <tr>
              <th>Col A</th>
              <th>Col B</th>
            </tr>
            <tr>
              <td>A1</td>
              <td>B1</td>
            </tr>
            <tr>
              <td>A2</td>
              <td>B2</td>
            </tr>
          </tbody>
        </table>
      );
    
      // Find the text of each element
      const text = $(<Table />)
        .find("tr")
        .not(":first-child")
        .array((item) => item.textContent);
    
      expect(text).toEqual(["A1B1", "A2B2"]);
    });
    

    Related

    • .filter(selector): a method that returns a new collection only with nodes that pass the matcher.
    • .is(selector): a method to determine whether all of the nodes match the selector.

    .parent()

    .parent() -> $
    

    Return a new collection with the direct parent of the current nodes. It also removes duplicates:

    it("can go down and up again", () => {
      const list = $(
        <ul>
          <li>A</li>
          <li>B</li>
        </ul>
      );
      const items = list.children(); // <li>A</li>, <li>B</li>
      const listB = items.parent(); // <ul>...</ul>
      expect(listB.html()).toEqual(list.html());
    });
    

    Parameters

    None.

    TODO? an optional filter?

    Return

    An instance of React Test with the parent node(s).

    Examples

    Find the parent node of all anchor tags:

    const list = $(
      <ul className="boo">
        <li className="bar">
          <a href="#" className="baz">
            Link 1
          </a>
        </li>
        <li className="foo">
          <a href="#" className="bar">
            Link 2
          </a>
        </li>
      </ul>
    );
    
    const parents = list.find("a").parent();
    expect(parents.nodes).toHaveLength(2);
    

    .props()

    .props(newProps) -> $
    

    Rerender the component with the new props specified as a plain object:

    const Demo = (props) => <div {...props}>world</div>;
    
    it("can force-update the props on the root", () => {
      const demo = $(<Demo className="hello" />);
      expect(demo).toHaveHtml(`<div class="hello">world</div>`);
      // Rerender with a new className on the top component:
      demo.props({ className: "bye" });
      expect(demo).toHaveHtml(`<div class="bye">world</div>`);
    });
    

    Parameters

    newProps: the props to pass to the component in a new re-render. It can be either a plain object, or a function that will receive the old props and should return the new props.

    Return

    An instance of React Test that has re-rendered with the new props.

    Examples

    Update the prop className:

    const Demo = ({ className }) => <div className={className}>world</div>;
    
    it("can inject new props", async () => {
      const demo = $(<Demo className="hello" />);
      expect(demo).toHaveHtml(`<div class="hello">world</div>`);
      demo.props({ className: "bye" });
      expect(demo).toHaveHtml(`<div class="bye">world</div>`);
    });
    
    it("can accept the old props", async () => {
      const demo = $(<Demo className="hello" />);
      expect(demo).toHaveHtml(`<div class="hello">world</div>`);
      demo.props((p) => ({ className: p.className + "-bye" }));
      expect(demo).toHaveHtml(`<div class="hello-bye">world</div>`);
    });
    

    .render()

    .render(newComponent) -> $
    

    Rerender the component as specified with the new value. If the component is different, or unmount+mount those respectively:

    const Demo = (props) => <div {...props}>world</div>;
    
    it("can force-update the props on the root", () => {
      const demo = $(<Demo className="hello" />);
      expect(demo).toHaveHtml(`<div class="hello">world</div>`);
      // Rerender with a new className on the top component:
      demo.props(<Demo className="bye" />);
      expect(demo).toHaveHtml(`<div class="bye">world</div>`);
    });
    

    Note: if you only want to re-render changing the props, you might prefer using .props().

    Parameters

    newComponent: the new component to render in place of the old one. If it's the same component, it'll trigger a re-render, otherwise it'll unmount the old one and mount the new one.

    Return

    An instance of React Test that has re-rendered with the new component.

    Examples

    Rerender with a new prop:

    const Demo = ({ className }) => <div className={className}>world</div>;
    
    it("can inject new props", async () => {
      const demo = $(<Demo className="hello" />);
      expect(demo).toHaveHtml(`<div class="hello">world</div>`);
      demo.render(<Demo className="bye" />);
      expect(demo).toHaveHtml(`<div class="bye">world</div>`);
    });
    
    it("can render a different component", () => {
      const demo = $(<div>Hello</div>);
      expect(demo).toHaveHtml(`<div>Hello</div>`);
      demo.render(<span>Bye</span>);
      expect(demo).toHaveHtml(`<span>Bye</span>`);
    });
    

    Notes

    Do not reuse a root node (by using this .render()) more than necessary (e.g. testing rerenders); instead create a raw instance of a component with $() as usual for testing new components.

    .siblings()

    .siblings(selector?) -> $
    

    Return a new collection with the direct parent of the current nodes with an optional filter:

    const list = $(<List />);
    const items = list.find("li.active").siblings();
    expect(items.array("className")).toEqual(["", ""]);
    

    Parameters

    selector: a string containing a selector that nodes must pass or a function that return a boolean. See .filter() for a complete explanation of how selectors work.

    Return

    An instance of React Test with the new siblings as nodes

    Examples

    Find all the items in the list that are not active:

    const list = $(<List />);
    const items = list.find("li.active").siblings();
    expect(items.array("className")).toEqual(["", ""]);
    

    .submit()

    .submit() -> Promise
    

    Trigger a form submission on all the matched forms. It should be awaited for the side effects to run and the component to re-rendered:

    const CreateUser = ({ onSubmit }) => (
      <form
        onSubmit={(e) => {
          e.preventDefault(); // <- this is required _when testing_
          onSubmit();
        }}
      >
        <input name="firstname" />
        <input name="lastname" />
        <input name="age" />
        <button>Submit</button>
      </form>
    );
    
    it("can mock submitting a form", async () => {
      const onSubmit = jest.fn();
      const createUser = $(<CreateUser onSubmit={onSubmit} />);
      expect(onSubmit).not.toBeCalled();
      await createUser.submit();
      expect(onSubmit).toBeCalled();
    });
    

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

    onSubmit should always call e.preventDefault(), since the browser behavior has not been imitated by this library.

    Parameters

    None. Any parameters passed will be ignored.

    Returns

    A promise that must be awaited before doing any assertion.

    Examples

    We can also test the submission by e.g. clicking on a button on a child node:

    const CreateUser = ({ onSubmit }) => (
      <form
        onSubmit={(e) => {
          e.preventDefault(); // <- this is required _when testing_
          onSubmit();
        }}
      >
        <input name="firstname" />
        <input name="lastname" />
        <input name="age" />
        <button>Submit</button>
      </form>
    );
    
    it("submits the form when clicking the button", async () => {
      const onSubmit = jest.fn();
      const createUser = $(<CreateUser onSubmit={onSubmit} />);
      expect(onSubmit).not.toBeCalled();
      await createUser.find("button").click();
      expect(onSubmit).toBeCalled();
    });
    

    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.

    .text()

    .text() -> String
    

    Get the textContent of the first matched node:

    it("can get the simple text", () => {
      const greeting = $(
        <div>
          Hello <br /> world
        </div>
      );
      expect(greeting.text()).toBe("Hello world");
    });
    

    .trigger()

    .trigger(name, extra?) -> Promise
    

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

    it("can simulate clicking a div in a specific place", async () => {
      const fn = jest.fn();
      const canvas = $(<canvas onClick={fn}></canvas>);
      await canvas.trigger("click", { clientX: 10, clientY: 20 });
      const event = fn.mock.calls[0][0];
      expect(event).toMatchObject({ clientX: 10, clientY: 20 });
    });
    

    Parameters

    • name: the event name. It should be in lowercase and without any on. Examples: "click", "keypress", "mousedown", "pointermove", etc.
    • extra = {}: any data that you want to mock into the event that the event handler will receive. This is very useful to mock e.g. clientX+clientY, target, etc.

    Returns

    A promise that must be awaited before doing any assertion.

    Examples

    You can test window.addEventListerner() the same way, since events bubble up to the global window by default:

    const Demo = ({ onDown }) => {
      useEffect(() => {
        window.addEventListener("keydown", onDown);
        return () => window.removeEventListener("keydown", onDown);
      }, [onDown]);
      return <div>Hello</div>;
    };
    
    it("can trigger clicks even from the window", async () => {
      const onDown = jest.fn();
      const demo = $(<Demo onDown={onDown} />);
      expect(onDown).not.toBeCalled();
    
      await demo.trigger("keydown", { key: "x" });
      expect(onDown).toBeCalled();
    
      const event = onDown.mock.calls[0][0];
      expect(event.key).toBe("x");
    });
    

    But you might want to customize the event more to make it more realistic, like changing the target manually (same Demo as above):

    it("can customize the target to window", async () => {
      const onDown = jest.fn();
      const demo = $(<Demo onDown={onDown} />);
      expect(onDown).not.toBeCalled();
    
      await demo.trigger("keydown", { key: "x", target: window });
      expect(onDown).toBeCalled();
    
      const event = onDown.mock.calls[0][0];
      expect(event.key).toBe("x");
      expect(event.target).toBe(window);
    });
    

    .type()

    .type(text) -> Promise
    

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

    it("can simulate typing in an input", async () => {
      const input = $(<input />);
      expect(input).toHaveValue("");
      await input.type("Francisco");
      expect(input).toHaveValue("Francisco");
    });
    

    Note that this simulates typing the text letter by letter, so it's useful to test more complex interactions. If you want to test a simpler onChange, you might want to use .change(text) instead.

    .type() 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

    • text: the new value to be sent to the callback as part of the event.

    Returns

    A promise that must be awaited before doing any assertion.

    Examples

    A simple controlled input:

    const Input = () => {
      const [text, setText] = useState("");
      return <input value={text} onChange={(e) => setText(e.target.value)} />;
    };
    
    const input = $(<Input />);
    expect(input).toHaveValue("");
    await input.type("Francisco");
    expect(input).toHaveValue("Francisco");
    

    A full component greeting whoever it's there:

    const Greeter = () => {
      const [name, setName] = useState("");
      return (
        <div>
          Hello {name || "Anonymous"}
          <input value={name} onChange={(e) => setName(e.target.value)} />
        </div>
      );
    };
    
    it("clicks the current element", async () => {
      const greet = $(<Greeter />);
      expect(greet.text()).toBe("Hello Anonymous");
      await greet.find("input").type("Francisco");
      expect(greet.text()).toBe("Hello Francisco");
    });
    

    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.

    Helpers

    act()

    When you want to do some operation that will trigger a change in your React component, usually you need to wrap it in act() to trigger the re-render. With React-Test, this is mostly not needed since all of our methods already wrap things with act() internally. But you might define your own e.g. delay() function, and if you expect some changes in your component during that timeout then you should wrap it with act():

    import $, { act } from "react-test";
    import CountDown from "./CountDown";
    
    const delay = (time) => new Promise((done) => setTimeout(done, time));
    
    it("will countdown from 3 to 0", async () => {
      const down = $(<CountDown />);
      expect(down).toHaveText("3");
      await act(() => delay(4000));
      expect(down).toHaveText("Done!");
    });
    

    If you do not wrap that custom delay with act(), you'll receive this warning:

    Warning: An update to Countdown inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at Countdown ([omitted]/Countdown.test.js:8:27)
    

    Parameters

    async function: a function that can be either async or sync, that once is done executing might trigger a change in your React component.

    Return

    Promise: a promise that must be awaited for to ensure the code has been updated properly.

    Notes

    You do not need this for any of the React methods, since we already wrap them with act() internally. So all of these are wrong:

    const counter = $(<Counter />);
    const down = $(<CountDown />);
    
    // WRONG; we already wrap these with act() internally
    await act(() => counter.click());
    await act(() => down.delay(4000));
    await act(() => until(() => down.text() === "Done!"));
    
    // RIGHT; this is how you should be using them instead
    await counter.click();
    await down.delay(4000);
    await until(() => down.text() === "Done!");
    

    Examples

    If you want to define your own timer, then you can wrap it in act():

    const delay = (time) => new Promise((done) => setTimeout(done, time));
    
    it('waits properly with act()', () => {
      const down = $(<CountDown />);
      expect(down).toHaveText('3');
      await act(() => delay(4000));
      expect(down).toHaveText('Done!');
    });
    

    until()

    Wait until the specified condition is fulfilled. There are multiple ways of specifying the conditions. For example, let's say that you have a timer that changes its class to active after 3s:

    import $, { until } from "react-test";
    
    const timer = $(<Timer />);
    
    expect(timer).not.toHaveClass("active");
    await until(() => timer.is(".active")); // Wait until it becomes active
    expect(timer).toHaveClass("active");
    

    Parameters

    checker: it receives either a callback or a component and only resolves when:

    • Receiving a callback, that callback returns true or a truthy value.
    • Receiving a component, we call data methods like .is('.active') that will resolve when it's "true".
    • Receiving a component, we can execute actions like .find('.active') and it'll be truthy when it finds at least 1 node that matches the query.

    Returns

    It returns a composite response that behaves both as a React Instance and as a Promise. So it can be awaited straight away, concatenated until you use a "data" method which will behave as a promise or transversing the DOM in which case it'll resolve when it's not empty:

    // Plain callback, resolves when it returns truthy
    await until(() => button.text() === "Hello!");
    
    // Wait until it has the class "active"
    await until(button).is(".active");
    
    // Wait until the collection returns a non-empty list
    await until(list).find("li");
    

    Examples

    // Keep pinging until the callback returns a truthy value
    await until(() => timer.is(".active"));
    
    // DATA methods, when one becomes "true" (or non-empty) it finishes executing
    await until(timer).is(".active"); // Same, finish when timer gets the class
    await until(timer).text(); // When the timer returns any text, finish
    
    // DOM MANIPULATION methods; when it returns a non-empty collection it finishes
    await until(timer).filter(".active"); // Same, finish when timer gets the class
    await until(timer).children("li"); // Finishes when the first <li> is appended
    
    // DOM MANIPULATION + chaining with DATA methods
    // Finish when the container becomes active
    await until(timer).find(".container").is(".active");
    // Any child becomes active
    await until(timer).children().is(".active");
    
    // DOM MANIPULATION + chaining with other DOM MANIPULATION methods:
    // Finishes when finding an active link
    await until(timer).find("a").filter(".active");
    // Finishes when one important child becomes active
    await until(timer).children().filter(".important.active");
    

    When there is a "DOM Manipulation" method, it'll finish executing when it returns a non-zero collection of items.

    When it's reading data, it'll finish executing when it returns truthy.

    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("value", /^\d+$/);
    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 and 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", "3");
    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 "banana"
    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");
    
    expect($checkbox.get(0).checked).toBe(true);
    expect($radio.get(0).checked).toBe(true);
    

    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().
    • <Signup />: to see handling of forms
    • <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");
      });
    });
    

    <Signup />

    We are going to see now how we can simulate interactions with a form. This goes from the basics of typing text, validating the output, to more advanced features like validating as we type. We are going to use the library form-mate to greatly simplify our code, our base form is this:

    import Form from "form-mate";
    
    export default function Signup({ onSubmit = () => {} }) {
      return (
        <Form onSubmit={onSubmit}>
          <input name="username" type="text" />
          <input name="tos" type="checkbox" />
          <input name="option" type="radio" value="a" defaultChecked />
          <input name="option" type="radio" value="b" />
          <button>Send</button>
        </Form>
      );
    }
    

    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:

    // Signup.test.js
    import React from "react";
    import $ from "react-test";
    import Signup from "./Signup";
    
    describe("Signup.js", () => {
      // Write your tests here
      // All of the examples below should go here
    });
    

    First let's check what happens if the user presses the submit button without changing anything:

    it("can be submitted empty", async () => {
      const cb = jest.fn();
      const form = $(<Signup onSubmit={cb} />);
    
      expect(cb).not.toBeCalled();
      await form.submit();
      expect(cb).toBeCalledWith({ username: "", option: "a" });
    });
    

    Perfect, we define a callback with Jest to mock the onSubmit action, and we see that the data is parsed correctly and submitted as an empty username and the option "a" which is selected by default.

    Now let's simulate that the user modifies the form, we use .type() for the text fields and just .click() for the checkbox and radio:

    it("can modify each of the fields properly", async () => {
      const cb = jest.fn();
      const form = $(<Signup onSubmit={cb} />);
    
      await form.find('[type="text"]').type("hello");
      await form.find('[type="checkbox"]').click();
      await form.find('[type="radio"][value="b"]').click();
      await form.submit();
      expect(cb).toBeCalledWith({ username: "hello", tos: "on", option: "b" });
    });