Skip to content

Instantly share code, notes, and snippets.

@jrmmendes
Created February 21, 2025 11:55
Show Gist options
  • Save jrmmendes/8e561858d0861158f948364e77f5ced8 to your computer and use it in GitHub Desktop.
Save jrmmendes/8e561858d0861158f948364e77f5ced8 to your computer and use it in GitHub Desktop.
Nock inspired http mocker
import { HttpMockClientBuilder } from "./testing/http-mock-client-builder.ts";
import { TestDataBuilder } from "./testing/test-data-builder.ts";
const builder = new TestDataBuilder<{ id: string; name: string }>({
id: () => "123",
name: () => "Mendes",
});
const client = new HttpMockClientBuilder()
.get("/")
.headers({ authorization: "access-token" })
.query({ personCode: 123 })
.reply(200, builder.build())
.build();
try {
const response = await client.get({
url: "/",
headers: {
authorization: "access-token",
},
method: "GET",
params: {
personCode: 123,
},
});
console.log(response.data);
} catch (e) {
console.log('error', e);
}
import { HttpMockClient } from "./http-mock-client.ts";
import type { MockElement } from "./types.ts";
export class HttpMockClientBuilder {
mocks: MockElement[] = [];
currentMock: Partial<MockElement> = {};
headers(headers: Record<string, string>) {
this.currentMock.headers = headers;
return this;
}
query<T extends Record<string, unknown>>(params: T) {
this.currentMock.params = params;
return this;
}
method(name: string, url: string = "/") {
this.currentMock.url = url;
this.currentMock.method = name.toUpperCase();
return this;
}
get(url?: string) {
return this.method("GET", url);
}
post(url?: string) {
return this.method("POST", url);
}
put(url?: string) {
return this.method("PUT", url);
}
patch(url?: string) {
return this.method("PATCH", url);
}
delete(url?: string) {
return this.method("DELETE", url);
}
reply<T extends object>(status: number, data?: T) {
this.currentMock.response = { status, data };
this.saveCurrentMock();
return this;
}
saveCurrentMock() {
if (!this.currentMock.response) {
throw Error("Invalid mock value");
}
this.mocks.push(this.currentMock as MockElement);
this.currentMock = {};
}
build(): HttpMockClient {
return new HttpMockClient(this.mocks);
}
}
import type {
HttpClientRequestParams,
HttpResponse,
MockElement,
} from "./types.ts";
export class HttpMockClient {
mocks: MockElement[] = [];
constructor(mocks: MockElement[] = []) {
this.mocks = mocks;
}
validateStatus(code: number) {
return code >= 199 && code <= 499;
}
getMock<T>({
method,
url,
params,
headers,
}: HttpClientRequestParams): HttpResponse<T> | void {
const mock = this.mocks.find(
(mock) =>
mock.method === method &&
mock.url === url &&
Object.keys(mock.params).reduce(
(isParamsMatching, name) =>
isParamsMatching && mock.params[name] === params?.[name],
true,
) &&
Object.keys(mock.headers).reduce(
(isHeadersMatching, name) =>
isHeadersMatching && mock.headers[name] === headers?.[name],
true,
),
);
if (mock) return mock.response as HttpResponse<T>;
}
request<T = unknown>({
url,
params,
headers,
method,
}: HttpClientRequestParams): Promise<HttpResponse<T>> {
const mock = this.getMock<T>({ url, params, headers, method }) ?? {
status: 404,
};
return this.validateStatus(mock.status)
? Promise.resolve(mock)
: Promise.reject(mock);
}
get<T = unknown>(params: HttpClientRequestParams): Promise<HttpResponse<T>> {
return this.request<T>({ ...params, method: "GET" });
}
post<T = unknown>(params: HttpClientRequestParams): Promise<HttpResponse<T>> {
return this.request<T>({ ...params, method: "POST" });
}
put<T = unknown>(params: HttpClientRequestParams): Promise<HttpResponse<T>> {
return this.request<T>({ ...params, method: "PUT" });
}
patch<T = unknown>(
params: HttpClientRequestParams,
): Promise<HttpResponse<T>> {
return this.request<T>({ ...params, method: "PATCH" });
}
delete<T = unknown>(
params: HttpClientRequestParams,
): Promise<HttpResponse<T>> {
return this.request<T>({ ...params, method: "DELETE" });
}
}
export type HttpClientRequestParams = {
url: string;
method: string;
params?: Record<string | symbol, unknown>;
headers?: Record<string, string>;
};
export type HttpResponse<T = unknown> = {
status: number;
data?: T;
};
export type MockElement = {
method: string;
url: string;
params: Record<string, unknown>;
headers: Record<string, string>;
response: {
status: number;
data: unknown;
};
};
export type FactoryOrValue<T = unknown> = () => T | T;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment