Skip to content

Mocking Browser Requests

The global fetch() function is available in many JavaScript runtimes, including server-side runtimes and web browsers. While fetch() works similarly in both environments, there are some important differences to take into account when testing code that uses fetch() in a browser environment.

Relative URLs with fetch()

Perhaps the most significant difference between using fetch() in a browser as opposed to a server environment is how relative URLs are handled. When you use fetch() in a browser, relative URLs are resolved relative to the current page’s URL. This means that if you call fetch("/api/data") from a page at https://example.com/page, the request will be made to https://example.com/api/data. This happens automatically in the browser whenever you use fetch(), ensuring that requests go to the correct server.

If you try to use a relative URL with fetch() in a server-side environment, you’ll get an error. This is because the server doesn’t know what the base URL should be, so it can’t resolve the relative URL. That means the same fetch() call that works in the browser won’t work in a server environment and it’s important to keep that in mind when writing tests.

Mock Browser Requests with Mentoss

Mentoss provides a way to mock browser requests in your tests, allowing you to test code that uses fetch() without making actual network requests. To mock a browser request, you can provide a baseUrl option to the FetchMocker constructor. This option specifies the base URL that relative URLs should be resolved against. You can then call a mocked fetch() using a relative URL. Here’s an example:

import { MockServer, FetchMocker } from "mentoss";
import { expect } from "chai";
const BASE_URL = "https://api.example.com";
describe("My API", () => {
const server = new MockServer(BASE_URL);
const mocker = new FetchMocker({
servers: [server],
baseUrl: BASE_URL,
});
// extract the fetch function
const myFetch = mocker.fetch;
// reset the mocker after each test
afterEach(() => {
mocker.clearAll();
});
it("should return a 200 status code", async () => {
// set up the route to test
server.get("/ping", 200);
// make the request
const response = await myFetch("/ping");
// check the response
expect(response.status).to.equal(200);
});
});

In this example, the baseUrl option is set to "https://api.example.com", which means that relative URLs will be resolved against that base URL. The test then calls myFetch("/ping"), which resolves to "https://api.example.com/ping" and makes a request to the server. This allows you to test code that uses relative URLs with fetch() in a browser-like environment.

Mock Credentialed Requests

A common use case for browser requests is to include credentials, such as cookies or HTTP authentication, with the request. This is often necessary for making authenticated requests to APIs. Mentoss provides a way to mock these credentialed requests using the credentials option in the FetchMocker constructor. This option allows you to specify a list of credentials that should be included with each request.

The CookieCredentials class allows you to mock cookie-based authentication in your tests. This is useful when testing applications that use cookies for maintaining session state or authentication.

Create a CookieCredentials Instance

You can create a new CookieCredentials instance either with or without a base URL:

import { CookieCredentials } from "mentoss";
// Create credentials for a specific domain
const credentials = new CookieCredentials("https://example.com");
// Or create credentials without a domain
const genericCredentials = new CookieCredentials();

When you create credentials with a base URL, all cookies will be automatically associated with that domain. Without a base URL, you’ll need to specify the domain for each cookie.

Setting Cookies

You can set cookies using the setCookie() method. Each cookie requires at least a name and value:

// With a base URL
const credentials = new CookieCredentials("https://example.com");
credentials.setCookie({
name: "sessionId",
value: "abc123"
});
// Without a base URL
const genericCredentials = new CookieCredentials();
genericCredentials.setCookie({
name: "sessionId",
value: "abc123",
domain: "example.com" // domain is required when no base URL is set
});

You can also specify additional cookie attributes:

credentials.setCookie({
name: "sessionId",
value: "abc123",
path: "/admin", // defaults to "/"
secure: true, // defaults to false
httpOnly: true // defaults to false
});

Delete Cookies

To delete a cookie, use the deleteCookie() method with the same information used to set it (except for the value):

credentials.deleteCookie({
name: "sessionId",
});

If you included a domain or path when setting the cookie, you must include it when deleting the cookie as well, as in this example:

credentials.deleteCookie({
name: "sessionId",
domain: "example.com",
});

Use CookieCredentials with FetchMocker

The CookieCredentials class is designed to work with FetchMocker for testing authenticated requests. When provided, FetchMocker will automatically include the specified cookies in requests to matching domains and paths, simulating a real browser environment. Here’s a complete example:

import { MockServer, FetchMocker, CookieCredentials } from "mentoss";
const BASE_URL = "https://example.com";
describe("Authenticated API", () => {
const server = new MockServer(BASE_URL);
const credentials = new CookieCredentials(BASE_URL);
const mocker = new FetchMocker({
servers: [server],
credentials: [credentials],
baseUrl: BASE_URL,
});
beforeEach(() => {
// Set up authentication cookie
credentials.setCookie({
name: "sessionId",
value: "abc123"
});
});
afterEach(() => {
mocker.clearAll();
});
it("should make authenticated request", async () => {
server.get({
url: "/api/data",
headers: {
Cookie: "sessionId=abc123"
}
}, {
status: 200,
body: { message: "success" }
});
const response = await mocker.fetch("/api/data");
expect(response.status).to.equal(200);
});
});

In this example, the cookie will be automatically included in all requests to example.com.

Cookies are included in requests based on these rules:

  1. The request domain must match or be a subdomain of the cookie’s domain
  2. The request path must match or be under the cookie’s path
  3. For secure cookies, the request must use HTTPS

For example:

const credentials = new CookieCredentials("https://example.com");
// Set a secure cookie for /admin path
credentials.setCookie({
name: "adminSession",
value: "xyz789",
path: "/admin",
secure: true
});
// Will include cookie (matches domain, path, and protocol)
await fetch("https://example.com/admin/users");
// Won't include cookie (wrong protocol)
await fetch("http://example.com/admin/users");
// Won't include cookie (wrong path)
await fetch("https://example.com/user/profile");
// Will include cookie (subdomain is allowed)
await fetch("https://sub.example.com/admin/users");