UNPKG

syntropylog

Version:

An instance manager with observability for Node.js applications

242 lines (236 loc) • 8.44 kB
/** * MockHttpClient - Framework Agnostic Mock * * This mock provides a testing-agnostic version of IHttpClientAdapter * that can be used with both Vitest and Jest without conflicts. */ /** * Creates a simple agnostic mock function without spy capabilities */ function createAgnosticMockFn(implementation) { const mockFn = (...args) => { if (implementation) { return implementation(...args); } return undefined; }; // Basic mock properties mockFn.mockClear = () => { }; mockFn.mockReset = () => { }; mockFn.mockImplementation = (impl) => { return createAgnosticMockFn(impl); }; mockFn.mockReturnValue = (value) => { return createAgnosticMockFn(() => value); }; mockFn.mockResolvedValue = (value) => { return createAgnosticMockFn(() => Promise.resolve(value)); }; mockFn.mockRejectedValue = (value) => { return createAgnosticMockFn(() => Promise.reject(value)); }; return mockFn; } export class MockHttpClient { spyFn = null; timeouts = new Map(); // Core methods - will be initialized in constructor request; get; post; put; delete; patch; setResponse; setError; setTimeout; reset; constructor(spyFn) { this.spyFn = spyFn || null; // Initialize mocks after spyFn is set this.request = this.createMock().mockImplementation(async (request) => { // Default successful response return { statusCode: 200, data: { message: 'Mock response' }, headers: { 'content-type': 'application/json' }, }; }); this.get = this.createMock().mockImplementation(async (url, headers) => { return this.request({ url, method: 'GET', headers: headers || {}, }); }); this.post = this.createMock().mockImplementation(async (url, body, headers) => { return this.request({ url, method: 'POST', headers: headers || {}, body, }); }); this.put = this.createMock().mockImplementation(async (url, body, headers) => { return this.request({ url, method: 'PUT', headers: headers || {}, body, }); }); this.delete = this.createMock().mockImplementation(async (url, headers) => { return this.request({ url, method: 'DELETE', headers: headers || {}, }); }); this.patch = this.createMock().mockImplementation(async (url, body, headers) => { return this.request({ url, method: 'PATCH', headers: headers || {}, body, }); }); // Initialize method implementations this.updateMethodImplementations(); this.setResponse = this.createMock().mockImplementation((method, response) => { // Configure the request method to return the specified response this.request.mockImplementation(async (req) => { if (req.method.toUpperCase() === method.toUpperCase()) { return response; } // Default response for other methods return { statusCode: 200, data: { message: 'Mock response' }, headers: { 'content-type': 'application/json' }, }; }); // Also update individual method implementations this.updateMethodImplementations(); }); this.setError = this.createMock().mockImplementation((method, error) => { // Configure the request method to throw the specified error this.request.mockImplementation(async (req) => { if (req.method.toUpperCase() === method.toUpperCase()) { const adapterError = { name: error.name, message: error.message, stack: error.stack, request: req, isAdapterError: true, }; throw adapterError; } // Default response for other methods return { statusCode: 200, data: { message: 'Mock response' }, headers: { 'content-type': 'application/json' }, }; }); // Also update individual method implementations this.updateMethodImplementations(); }); this.setTimeout = this.createMock().mockImplementation((method, timeoutMs) => { this.timeouts.set(method, timeoutMs); // Configure the request method to timeout this.request.mockImplementation(async (req) => { if (req.method.toUpperCase() === method.toUpperCase() && this.timeouts.has(method)) { await new Promise((resolve) => setTimeout(resolve, this.timeouts.get(method) + 10)); throw new Error(`Mock HTTP client timed out after ${this.timeouts.get(method)}ms`); } // Default response for other methods return { statusCode: 200, data: { message: 'Mock response' }, headers: { 'content-type': 'application/json' }, }; }); // Also update individual method implementations this.updateMethodImplementations(); }); this.reset = this.createMock().mockImplementation(() => { this.timeouts.clear(); this.request.mockReset(); this.get.mockReset(); this.post.mockReset(); this.put.mockReset(); this.delete.mockReset(); this.patch.mockReset(); // Restore default implementations this.request.mockImplementation(async (request) => { return { statusCode: 200, data: { message: 'Mock response' }, headers: { 'content-type': 'application/json' }, }; }); this.updateMethodImplementations(); }); } updateMethodImplementations() { this.get.mockImplementation(async (url, headers) => { return this.request({ url, method: 'GET', headers: headers || {}, }); }); this.post.mockImplementation(async (url, body, headers) => { return this.request({ url, method: 'POST', headers: headers || {}, body, }); }); this.put.mockImplementation(async (url, body, headers) => { return this.request({ url, method: 'PUT', headers: headers || {}, body, }); }); this.delete.mockImplementation(async (url, headers) => { return this.request({ url, method: 'DELETE', headers: headers || {}, }); }); this.patch.mockImplementation(async (url, body, headers) => { return this.request({ url, method: 'PATCH', headers: headers || {}, body, }); }); } createMock(implementation) { if (!this.spyFn) { throw new Error(` 🚨 SPY FUNCTION NOT INJECTED! 😔 To use spy functions like toHaveBeenCalled(), toHaveBeenCalledWith(), etc. YOU MUST inject your spy function in the constructor: // For Vitest: const mockHttp = new MockHttpClient(vi.fn); // For Jest: const mockHttp = new MockHttpClient(jest.fn); // For Jasmine: const mockHttp = new MockHttpClient(jasmine.createSpy); // Without spy (basic functionality only): const mockHttp = new MockHttpClient(); DON'T FORGET AGAIN! 😤 `); } return this.spyFn(implementation); } } //# sourceMappingURL=MockHttpClient.js.map