Test Remix loaders and actions
If you are using Remix, most of your application code will live in the loaders and actions instead of React components,
Knowing that, we could use Jest to test them isolated from the rest of the application.
The general idea is that a loader or an action is a function constantly receiving the same parameters:
- A Request object
- A Params object
- A Context object
And it always returns a Promise with some data or a Response. It may throw errors or Responses too.
Testing Loaders
import { loader } from "path/to/route";
describe("Path Loader", () => {
it("should return a response", async () => {
const response = await loader({
request: new Request("http://app.com/path"),
params: {},
context: {},
});
expect(response).toBeInstanceOf(Response);
});
});
This is a simple test, but it helps show how to run a loader by simply calling it the correct params. We could do more things. Let's say we want to test what happens if the user is authenticated or not.
import { loader } from "path/to/route";
import { getSession, commitSession } from "path/to/session";
describe("Path Loader", () => {
it("should return a 200 Ok if user is authenticated", async () => {
let session = await getSession();
session.set("user", { id: 1 });
let request = new Request("http://app.com/path", {
headers: { cookie: await commitSession(session) },
});
const response = await loader({ request, params: {}, context: {} });
expect(response.status).toBe(200);
});
it("should return a 401 Unauthorized if user is not authenticated", async () => {
const response = await loader({
request: new Request("http://app.com/path"),
params: {},
context: {},
});
expect(response.status).toBe(200);
});
});
And, of course, you can test the data returned by parsing the Response.
import { loader } from "path/to/route";
describe("Path Loader", () => {
it("should return an object with the user name", async () => {
const response = await loader({
request: new Request("http://app.com/path"),
params: {},
context: {},
});
let data = await response.json();
expect(data).toEqual({ user: { name: "Sergio" } });
});
});
Testing Actions
Testing an action function is almost the same. With a single difference, you need to set a method to not be GET, and you want to pass a body.
import { redirect } from "remix";
import { action } from "path/to/route";
describe("Path Action", () => {
it("should return a redirect", async () => {
let body = new URLSearchParams({
name: "Sergio",
});
let request = new Request("http://app.com/path", {
method: "POST",
body,
});
const response = await action({ request, params: {}, context: {} });
expect(response).toEqual(redirect("/new/path"));
});
});
And as you can see above, you could do expect(response).toEqual(anotherResponse)
; that way, it will ensure everything in the response match, status, body, headers, etc.
You can also test a specific header is defined.
import { redirect } from "remix";
import { action } from "path/to/route";
describe("Path Action", () => {
it("should send the Set-Cookie header", async () => {
let body = new URLSearchParams({
name: "Sergio",
});
let request = new Request("http://app.com/path", {
method: "POST",
body,
});
const response = await action({ request, params: {}, context: {} });
expect(response.headers.get("Set-Cookie")).toExists();
});
});
If the Set-Cookie
header is the session, you could even test the value stored there.
import { redirect } from "remix";
import { action } from "path/to/route";
import { getSession } from "path/to/session";
describe("Path Action", () => {
it("should set a cookie with the user name", async () => {
let body = new URLSearchParams({
name: "Sergio",
});
let request = new Request("http://app.com/path", {
method: "POST",
body,
});
const response = await action({ request, params: {}, context: {} });
let cookie = response.headers.get("Set-Cookie");
let session = await getSession(cookie);
expect(session.get("user")).toEqual({ name: "Sergio" });
});
});