render-beforeall
TypeScript icon, indicating that this package has built-in type declarations

1.0.3 • Public • Published

Installation

  • NPM: npm i -D render-beforeall
    YARN: yarn add render-beforeall -D
  • Import this library into your test file
    ⚠️ Be sure to import this library Before importing @testing-library/react, this is a limitation of react testing library as described here

Jest

  • Load it before all test using jest's setupFilesAfterEnv configuration.
    jest.config.json

    {
      "setupFilesAfterEnv": ["render-beforeall"]
    }
  • If you are using CRA you could import this in setupTests.js check this explanation

Mocha

By default this behaviour is not enabled in mocha, when using this library it will as this library takes control of when to cleanup and by default is afterEach test. But if your suite happened to behave like this, is probably because someone configured it already following this guide

so you will need to change:

mocha -r ./mocha-watch-cleanup-after-each.js

by

mocha -r render-beforeall

and that's it.

How To Use it

import { renderBeforeAll } from "render-beforeall";
import { render, screen } from "@testing-library/react";
// import ...

describe("UserProfile", () => {
  renderBeforeAll(() => <UserProfile />); // <-- just this line

  it(`should let the user know about new offers`, () => {
    expect(screen.getByText("New offers!")).toBeInTheDocument();
  });

  it(`should present user's address`, () => {
    expect(screen.getByText("1772 Test Street")).toBeInTheDocument();
  });

  // ...
});

Advanced use

Alternatively, you could control manually when the testing-library's cleanup function is called with the following example:

import { settings } from "render-beforeall";

settings.disableAutoCleanup();

// ... something

if (settings.isAutoCleanup()) {
  // do something when autoCleanup is on
}

settings.enableAutoCleanUp();

Best Practices

One State per describe

If your component has several states, avoid changing them as part of a test.
By not doing this you might violate F.I.R.S.T.

BAD example

describe(`UserProfile`, () => {
  renderBeforeAll(() => <UserProfile />);

  test(`shows purchased products when unfolding purchases section`, ...);

  test(`shows a product`, ...);

  // more purchases section tests ...

  test(`shows invoices when unfolding invoces section`, ...);

  test(`shows an invoice`, ...);

  // more invoices section tests ...
});

Good Example

describe(`UserProfile`, () => {
  describe(`Purchases`, () => {
    renderBeforeAll(() => <UserProfile />);
    beforeAll(() => userEvent.click(screen.getByText('Purchases')))

    test(`purchases section is shown`, ...);

    test(`shows a product`, ...);

    // more purchases section tests ...
  });

  describe(`Invoices`, () => {
    renderBeforeAll(() => <UserProfile />);
    beforeAll(() => userEvent.click(screen.getByText('Invoices')))

    test(`invoices section is shown`, ...);

    test(`shows an invoice`, ...);

    // more invoices section tests ...
  });
});

Description, notes, motivations and alternatives

react-testing-library render beforeAll test utility

This tool was created to address the lack of subtests in jest and the enforced (and very useful) rule of react-testing-library to clean rendered components in afterEach issue. Allowing to render once and run several tests against the same rendered state, following one test - one assertion principle.

NOTE This tool was not intended to improve performance and there are several caveats you need to take into account when using this tool.

You might violate F.I.R.S.T.

⚠️ WARNING ⚠️ You might violate F.I.R.S.T., the I (Isolated/Independent part) as you render once, a test could change the state (i.e. interacting with the rendered component) and so the test running after that state change might need to know of that precondition.

An alternative to using this library is Write fewer longer tests. Kent Dodds addresses the Isolation problem, and their's argument is that jest is good at pointing out what has failed in the test, which is true. Is my opinion that, jest's error messages are not good enough, I rather see the functional explanation of what got broken, rather than a couldn't find an element with text ... or couldn't find a button that should be expected (maybe because some business rule got broken and might not be that obvious).

Also when following TDD is way easier to follow one test - one assertion is part of the discipline. It's easier to stop work at any moment when you have the test functional description described than the GOD test with several scenarios without any description. Also, way easier to read in a code review, and particularly easier to extract the test report and share it with team members to reduce duplication tests.

I strongly believe that Kent Dodds approach could be ok in small and/or fast projects (MVP) that are not intended to be refactored often or that might die quickly. But in a continuous delivery project, where refactors and new feature additions are high, better to go with one test - one assertion principle for the reasons explained in that link.

Conclusion

It will be a way better solution to have jest subtests similar to python's unittest.subTest work

Rewriting Kent Dodds example:

test("course loads and renders the course information", async () => {
  const courseId = "123";
  const title = "My Awesome Course";
  const subtitle = "Learn super cool things";
  const topics = ["topic 1", "topic 2"];

  getCourseInfo.mockResolvedValueOnce(buildCourse({ title, subtitle, topics }));

  render(<Course courseId={courseId} />);

  subtest("should call the getCourseInfo function properly", () => {
    // danyg> please avoid contract tests you already testing this got properly called by asserting the side-effects of render
    expect(getCourseInfo).toHaveBeenCalledWith(courseId);
    expect(getCourseInfo).toHaveBeenCalledTimes(1);
  });

  subtest("should show a loading spinner", () => {
    const alert = screen.getByRole("alert");
    expect(alert).toHaveTextContent(/loading/i);
  });

  await subtest("should render the title", async () => {
    const titleEl = await screen.findByRole("heading");
    expect(titleEl).toHaveTextContent(title);
  });

  subtest("should render the subtitle", () => {
    expect(screen.getByText(subtitle)).toBeInTheDocument();
  });

  subtest("should render the list of topics", () => {
    const topicElsText = screen
      .getAllByRole("listitem")
      .map((el) => el.textContent);
    expect(topicElsText).toEqual(topics);
  });
});

This way FIRST wouldn't be broken and it will be easier to see what got broken and why.

Examples

Form

describe("RegisterForm", () => {
  describe("Shows the necessary fields", () => {
    renderBeforeAll(() => <RegisterForm />);

    it("shows a username input");

    it("shows a First Name input");

    it("shows a Last Name input");

    it("shows an Email input");

    it("shows an Repeat Email input");

    it("shows a Password input");

    it("shows a Repeat Password input");
  });

  describe("First Name Validations", () => {
    // these kind of test would change the state, do not use renderBeforeAll in this case FIRST could be violated
    it(`Shows an error First Name is empty`, () => {
      render(<RegisterForm />);
      // ...
    });

    it(`Shows an error First Name contain invalid characters`, () => {
      render(<RegisterForm />);
      // ...
    });

    // ...
  });

  // tho fields interacting, no need to use renderBeforeAll
  it(`Shows an error when Email and Repeat Email don't match`, () => {
    render(<RegisterForm />);
    // ...
  });

  it(`Shows an error when Password and Repeat Password don't match`, () => {
    render(<RegisterForm />);
    // ...
  });

  it(`Shows an error when First and Last Name conform an banned word or phrase`, () => {
    render(<RegisterForm />);
    // ...
  });
});

Several States

describe(`UserProfile`, () => {
  describe(`Purchases`, () => {
    renderBeforeAll(() => <UserProfile />);
    beforeAll(() => userEvent.click(screen.getByText('Purchases')))

    test(`purchases section is shown`, ...);

    test(`shows a product`, ...);

    // more purchases section tests ...
  });

  describe(`Invoices`, () => {
    renderBeforeAll(() => <UserProfile />);
    beforeAll(() => userEvent.click(screen.getByText('Invoices')))

    test(`invoices section is shown`, ...);

    test(`shows an invoice`, ...);

    // more invoices section tests ...
  });
});

Package Sidebar

Install

npm i render-beforeall

Weekly Downloads

0

Version

1.0.3

License

MIT

Unpacked Size

25.6 kB

Total Files

11

Last publish

Collaborators

  • danyg