wasp/web/docs/project/testing.md
Craig McIlwrath 60233dcbcc
Restructures docs (#1333)
Co-authored-by: Mihovil Ilakovac <mihovil@ilakovac.com>
Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
Co-authored-by: Filip Sodić <filip.sodic@gmail.com>
Co-authored-by: Filip Sodić <filip.sodic@fer.hr>
2023-08-11 16:47:49 +02:00

9.7 KiB

title
Testing

:::info Wasp is in beta, so keep in mind there might be some kinks / bugs, and possibly some changes with testing support in the future. If you encounter any issues, reach out to us on Discord and we will make sure to help you out! :::

Testing Your React App

Wasp enables you to quickly and easily write both unit tests and React component tests for your frontend code. Because Wasp uses Vite, we support testing web apps through Vitest.

Included Libraries

vitest: Unit test framework with native Vite support.

@vitest/ui: A nice UI for seeing your test results.

jsdom: A web browser test environment for Node.js.

@testing-library/react / @testing-library/jest-dom: Testing helpers.

msw: A server mocking library.

Writing Tests

For Wasp to pick up your tests, they should be placed within the src/client directory and use an extension that matches these glob patterns. Some of the file names that Wasp will pick up as tests:

  • yourFile.test.ts
  • YourComponent.spec.jsx

Within test files, you can import your other source files as usual. For example, if you have a component Counter.jsx, you test it by creating a file in the same directory called Counter.test.jsx and import the component with import Counter from './Counter'.

Running Tests

Running wasp test client will start Vitest in watch mode and recompile your Wasp project when changes are made.

  • If you want to see a realtime UI, pass --ui as an option.
  • To run the tests just once, use wasp test client run.

All arguments after wasp test client are passed directly to the Vitest CLI, so check out their documentation for all of the options.

:::warning Be Careful You should not run wasp test while wasp start is running. Both will try to compile your project to .wasp/out. :::

React Testing Helpers

Wasp provides several functions to help you write React tests:

  • renderInContext: Takes a React component, wraps it inside a QueryClientProvider and Router, and renders it. This is the function you should use to render components in your React component tests.

    import { renderInContext } from "@wasp/test";
    
    renderInContext(<MainPage />);
    
  • mockServer: Sets up the mock server and returns an object containing the mockQuery and mockApi utilities. This should be called outside of any test case, in each file that wants to use those helpers.

    import { mockServer } from "@wasp/test";
    
    const { mockQuery, mockApi } = mockServer();
    
    • mockQuery: Takes a Wasp query to mock and the JSON data it should return.

      import getTasks from "@wasp/queries/getTasks";
      
      mockQuery(getTasks, []);
      
      • Helpful when your component uses useQuery.
      • Behind the scenes, Wasp uses msw to create a server request handle that responds with the specified data.
      • Mock are cleared between each test.
    • mockApi: Similar to mockQuery, but for APIs. Instead of a Wasp query, it takes a route containing an HTTP method and a path.

      import { HttpMethod } from "@wasp/types";
      
      mockApi({ method: HttpMethod.Get, path: "/foor/bar" }, { res: "hello" });
      

Testing Your Server-Side Code

Wasp currently does not provide a way to test your server-side code, but we will be adding support soon. You can track the progress at this GitHub issue and express your interest by commenting.

Examples

You can see some tests in a Wasp project here.

Client Unit Tests

export function areThereAnyTasks(tasks) {
  return tasks.length === 0;
}
import { test, expect } from "vitest";

import { areThereAnyTasks } from "./helpers";

test("areThereAnyTasks", () => {
  expect(areThereAnyTasks([])).toBe(false);
});
import { Task } from "@wasp/entities";

export function areThereAnyTasks(tasks: Task[]): boolean {
  return tasks.length === 0;
}
import { test, expect } from "vitest";

import { areThereAnyTasks } from "./helpers";

test("areThereAnyTasks", () => {
  expect(areThereAnyTasks([])).toBe(false);
});

React Component Tests

import { useQuery } from "@wasp/queries";
import getTasks from "@wasp/queries/getTasks";

const Todo = (_props) => {
  const { data: tasks } = useQuery(getTasks);
  return (
    <ul>
      {tasks &&
        tasks.map((task) => (
          <li key={task.id}>
            <input type="checkbox" value={task.isDone} />
            {task.description}
          </li>
        ))}
    </ul>
  );
};
import { test, expect } from "vitest";
import { screen } from "@testing-library/react";

import { mockServer, renderInContext } from "@wasp/test";
import getTasks from "@wasp/queries/getTasks";
import Todo from "./Todo";

const { mockQuery } = mockServer();

const mockTasks = [
  {
    id: 1,
    description: "test todo 1",
    isDone: true,
    userId: 1,
  },
];

test("handles mock data", async () => {
  mockQuery(getTasks, mockTasks);

  renderInContext(<Todo />);

  await screen.findByText("test todo 1");

  expect(screen.getByRole("checkbox")).toBeChecked();

  screen.debug();
});
import { useQuery } from "@wasp/queries";
import getTasks from "@wasp/queries/getTasks";

const Todo = (_props: {}) => {
  const { data: tasks } = useQuery(getTasks);

  return (
    <ul>
      {tasks &&
        tasks.map((task) => (
          <li key={task.id}>
            <input type="checkbox" value={task.isDone} />
            {task.description}
          </li>
        ))}
    </ul>
  );
};
import { test, expect } from "vitest";
import { screen } from "@testing-library/react";

import { mockServer, renderInContext } from "@wasp/test";
import getTasks from "@wasp/queries/getTasks";
import Todo from "./Todo";

const { mockQuery } = mockServer();

const mockTasks = [
  {
    id: 1,
    description: "test todo 1",
    isDone: true,
    userId: 1,
  },
];

test("handles mock data", async () => {
  mockQuery(getTasks, mockTasks);

  renderInContext(<Todo />);

  await screen.findByText("test todo 1");

  expect(screen.getByRole("checkbox")).toBeChecked();

  screen.debug();
});

Testing With Mocked APIs

import api from "@wasp/api";

const Todo = (_props) => {
  const [tasks, setTasks] = useState([]);
  useEffect(() => {
    api
      .get("/tasks")
      .then((res) => res.json())
      .then((tasks) => setTasks(tasks))
      .catch((err) => window.alert(err));
  });

  return (
    <ul>
      {tasks &&
        tasks.map((task) => (
          <li key={task.id}>
            <input type="checkbox" value={task.isDone} />
            {task.description}
          </li>
        ))}
    </ul>
  );
};
import { test, expect } from "vitest";
import { screen } from "@testing-library/react";

import { mockServer, renderInContext } from "@wasp/test";
import Todo from "./Todo";

const { mockApi } = mockServer();

const mockTasks = [
  {
    id: 1,
    description: "test todo 1",
    isDone: true,
    userId: 1,
  },
];

test("handles mock data", async () => {
  mockApi("/tasks", { res: mockTasks });

  renderInContext(<Todo />);

  await screen.findByText("test todo 1");

  expect(screen.getByRole("checkbox")).toBeChecked();

  screen.debug();
});
import { Task } from "@wasp/entities";
import api from "@wasp/api";

const Todo = (_props: {}) => {
  const [tasks, setTasks] = useState<Task>([]);
  useEffect(() => {
    api
      .get("/tasks")
      .then((res) => res.json() as Task[])
      .then((tasks) => setTasks(tasks))
      .catch((err) => window.alert(err));
  });

  return (
    <ul>
      {tasks &&
        tasks.map((task) => (
          <li key={task.id}>
            <input type="checkbox" value={task.isDone} />
            {task.description}
          </li>
        ))}
    </ul>
  );
};
import { test, expect } from "vitest";
import { screen } from "@testing-library/react";

import { mockServer, renderInContext } from "@wasp/test";
import Todo from "./Todo";

const { mockApi } = mockServer();

const mockTasks = [
  {
    id: 1,
    description: "test todo 1",
    isDone: true,
    userId: 1,
  },
];

test("handles mock data", async () => {
  mockApi("/tasks", mockTasks);

  renderInContext(<Todo />);

  await screen.findByText("test todo 1");

  expect(screen.getByRole("checkbox")).toBeChecked();

  screen.debug();
});