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>
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 aQueryClientProvider
andRouter
, 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 themockQuery
andmockApi
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.
- Helpful when your component uses
-
mockApi
: Similar tomockQuery
, 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();
});