Mocking CORS Requests
Another important consideration when mocking browser requests is how to handle CORS (Cross-Origin Resource Sharing) requests. CORS is a security feature that restricts which domains can make requests to a server. When you make a request from one domain to another, the server must include the appropriate CORS headers to allow the request to go through. When you set the baseUrl
option in the FetchMocker
constructor, Mentoss automatically includes the appropriate CORS headers when a request is made to a different origin.
Simple Requests
For simple requests, Mentoss automatically includes the Origin
header on the request. If the server responds with the appropriate Access-Control-Allow-Origin
header, your code will receive the response as expected. If the server does not respond with the appropriate header, the request will fail. Here’s an example:
import { MockServer, FetchMocker } from "mentoss";
const BASE_URL = "https://api.example.com";
describe("My API", () => { const server = new MockServer("https://www.example.com"); const mocker = new FetchMocker({ servers: [server], baseUrl: BASE_URL, });
// extract the fetch function const myFetch = mocker.fetch;
it("should return a 200 status code", async () => { // set up the route to test server.get("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, }, body: { message: "pong" }, });
// make the request const response = await myFetch("https://www.example.com/ping");
// check the response expect(response.status).to.equal(200); });});
In this example, the server responds with the Access-Control-Allow-Origin
header set to the base URL "https://api.example.com"
. This allows the request to go through because the origin matches the origin of the fetch mocker’s baseUrl
, and the test passes.
Preflighted Requests
For preflighted requests, Mentoss automatically sends an OPTIONS
request to the server to check if the request is allowed. If the server responds with the appropriate Access-Control-Allow-Methods
and Access-Control-Allow-Headers
headers, the original request will go through. If the server does not respond with the appropriate headers, the request will fail. Here’s an example:
import { MockServer, FetchMocker } from "mentoss";
const BASE_URL = "https://api.example.com";
describe("My API", () => { const server = new MockServer("https://www.example.com"); const mocker = new FetchMocker({ servers: [server], baseUrl: BASE_URL, });
// extract the fetch function const myFetch = mocker.fetch;
it("should return a 200 status code", async () => { // this is for the preflight request server.options("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, "Access-Control-Allow-Headers": "Content-Type", }, });
// this is the route that we want to call server.get("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, }, body: { message: "pong" }, });
// make the request const response = await myFetch("https://www.example.com/ping", { headers: { "Content-Type": "application/json", }, });
// check the response expect(response.status).to.equal(200); });});
In this example, the code attempts a request to "https://www.example.com/ping"
. Because this is a cross-origin request, Mentoss first sends an OPTIONS
request to the server to check if the request is allowed. The server responds to the preflight request with the appropriate Access-Control-Allow-Origin
and Access-Control-Allow-Headers
headers, allowing the original request to go through.
The Preflight Cache
When a browser makes a preflight request, it caches the response for a period of time (usually 5 minutes but customizable using the Access-Control-Max-Age
header). This means that if you make the same request again within the cache period, the browser won’t send another preflight request. Instead, it will use the cached response to determine if the request is allowed. Mentoss does not currently support caching preflight responses, so each preflight request will result in a new preflight request to the server.
Mentoss caches preflight responses in memory for the duration of the test run. This means that if you make the same preflighted request multiple times within a single test, Mentoss will only send the OPTIONS
request once. However, you can manually clear the preflight cache by calling the clearPreflightCache()
method. It’s helpful to do this after each test
import { MockServer, FetchMocker } from "mentoss";
const BASE_URL = "https://api.example.com";
describe("My API", () => { const server = new MockServer("https://www.example.com"); const mocker = new FetchMocker({ servers: [server], baseUrl: BASE_URL, });
// extract the fetch function const myFetch = mocker.fetch;
// clear preflight cache entries afterEach(() => { mocker.clearPreflightCache(); });
it("should return a 200 status code", async () => { // this is for the preflight request server.options("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, "Access-Control-Allow-Headers": "Content-Type", }, });
// this is the route that we want to call server.get("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, }, body: { message: "pong" }, });
// make the request const response = await myFetch("https://www.example.com/ping", { headers: { "Content-Type": "application/json", }, });
// check the response expect(response.status).to.equal(200); });});
Credentialed CORS Requests
By default, credentials are not sent with CORS requests. If you want to include credentials with a CORS request, you must set the credentials
option to "include"
in the fetch()
call. The server can then decide whether or not to accept the credentials by sending Access-Control-Allow-Credentials: true
in the response. When that happens, the browser allows access to the response; otherwise, the browser blocks access to the response. This is true for both simple and preflighted requests.
Mentoss can be used to mock credentialed CORS requests by setting the credentials
option in the FetchMocker
constructor and ensuring that the server responds with the appropriate Access-Control-Allow-Credentials
header. Here’s an example:
import { MockServer, FetchMocker } from "mentoss";
const BASE_URL = "https://www.example.com";const API_URL = "https://api.example.com";
describe("My API", () => { const server = new MockServer(API_URL); const cookies = new CookieCredentials(API_URL); const mocker = new FetchMocker({ servers: [server], baseUrl: BASE_URL, credentials: [cookies], });
// extract the fetch function const myFetch = mocker.fetch;
it("should return a 200 status code", async () => {
// set up the route to test server.get("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, "Access-Control-Allow-Credentials": "true", }, body: { message: "pong" }, });
// make the request const response = await myFetch("https://www.example.com/ping");
// check the response expect(response.status).to.equal(200); });});
In this example, the server responds with the Access-Control-Allow-Credentials
header set to "true"
. This allows the request to go through because the server allows credentials to be included with the request.
Preflighted Requests with Credentials
When making a preflighted request with credentials, the server must respond with the appropriate Access-Control-Allow-Credentials
header in both the preflight response and the actual response. Here’s an example:
import { MockServer, FetchMocker } from "mentoss";
const BASE_URL = "https://www.example.com";const API_URL = "https://api.example.com";
describe("My API", () => { const server = new MockServer(API_URL); const cookies = new CookieCredentials(API_URL); const mocker = new FetchMocker({ servers: [server], baseUrl: BASE_URL, credentials: [cookies], });
// extract the fetch function const myFetch = mocker.fetch;
it("should return a 200 status code", async () => {
// this is for the preflight request server.options("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, "Access-Control-Allow-Headers": "Custom", "Access-Control-Allow-Credentials": "true", }, });
// this is the route that we want to call server.get("/ping", { status: 200, headers: { "Access-Control-Allow-Origin": BASE_URL, "Access-Control-Allow-Headers": "Custom", "Access-Control-Allow-Credentials": "true", }, body: { message: "pong" }, });
// make the request const response = await myFetch("https://www.example.com/ping", { headers: { Custom: "header", }, });
// check the response expect(response.status).to.equal(200); });});
In this example, the server responds to the preflight request with the appropriate Access-Control-Allow-Credentials
header. This allows the original request to go through because the server allows credentials to be included with the request. The server also responds to the original request with the appropriate Access-Control-Allow-Credentials
header, allowing the browser to access the response. If the server does not respond with the appropriate headers for either request, then the request will fail.