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 domainconst credentials = new CookieCredentials("https://example.com");
// Or create credentials without a domainconst 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 URLconst credentials = new CookieCredentials("https://example.com");credentials.setCookie({ name: "sessionId", value: "abc123"});
// Without a base URLconst 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
.
Cookie Matching Rules
Cookies are included in requests based on these rules:
- The request domain must match or be a subdomain of the cookie’s domain
- The request path must match or be under the cookie’s path
- For secure cookies, the request must use HTTPS
For example:
const credentials = new CookieCredentials("https://example.com");
// Set a secure cookie for /admin pathcredentials.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");