Skip to content

Testing Overview & MockHttpAdapter ​

@yildizpay/http-adapter ships a dedicated testing sub-path with purpose-built test doubles, spies, and no-op helpers. Import them without polluting your production bundle:

typescript
import {
  MockHttpAdapter,
  MockHttpClient,
  NoopInterceptor,
  SpyInterceptor,
  SpyObserver,
} from '@yildizpay/http-adapter/testing';

MockHttpAdapter ​

A full in-memory test double for HttpAdapter that implements HttpAdapterContract. Use it in unit tests to control exactly what adapter.send() returns — without making real HTTP calls.

Setting Up Responses ​

typescript
const adapter = new MockHttpAdapter();

// Always return this response
adapter.mockResolvedValue({ STATUS: 'SUCCESS', ORDER_ID: '123' });

// Always throw this error
adapter.mockRejectedValue(new ServiceUnavailableException(/* ... */));

// One-time responses consumed in FIFO order, with a fallback default
adapter
  .mockResolvedOnce({ STATUS: 'PENDING' })
  .mockResolvedOnce({ STATUS: 'SUCCESS' })
  .mockResolvedValue({ STATUS: 'UNKNOWN' });  // fallback after queue is exhausted

// Custom factory — receives the full Request object
adapter.mockImplementation((request) => ({
  STATUS: request.body?.type === 'REFUND' ? 'REFUNDED' : 'SUCCESS',
}));

Endpoint-Specific Mocking ​

Use onEndpoint() to scope responses and assertions to a single path. Endpoint-scoped responses take priority over global ones.

typescript
adapter.onEndpoint('/api/payments').mockResolvedValue({ STATUS: 'SUCCESS' });
adapter.onEndpoint('/api/refunds').mockRejectedValue(new NotFoundException(/* ... */));

// One-time queue per endpoint
adapter.onEndpoint('/api/payments')
  .mockResolvedOnce({ STATUS: 'PENDING' })
  .mockResolvedValue({ STATUS: 'SUCCESS' });  // fallback

Assertions ​

After running the code under test:

typescript
adapter.assertCalledTimes(2);
adapter.assertCalledWith('/api/payments', { method: HttpMethod.POST });
adapter.assertCalledWithBody(0, { AMOUNT: '100', CURRENCY: 'TRY' });
adapter.assertNthCalledWith(1, '/api/payments');
adapter.assertLastCalledWith('/api/refunds');
adapter.assertCallOrder('/api/payments', '/api/refunds');
adapter.assertNotCalled();

Convenience getters:

typescript
adapter.callCount;      // number
adapter.firstCall;      // Request | undefined
adapter.lastCall;       // Request | undefined
adapter.wasCalled();    // boolean
adapter.wasNotCalled(); // boolean

Endpoint scopes expose the same assertion API, scoped to their path:

typescript
const paymentsScope = adapter.onEndpoint('/api/payments');
paymentsScope.assertCalledTimes(1);
paymentsScope.assertCalledWith({ body: { MERCHANT_ID: 'M001' } });
paymentsScope.wasCalled(); // boolean

RequestMatcher — Partial Request Matching ​

All assertCalledWith variants accept an optional RequestMatcher for deep partial matching on method, body, headers, and queryParams. Only the fields you specify are checked; extra fields in the actual request are ignored.

typescript
adapter.assertCalledWith('/api/payments', {
  method: HttpMethod.POST,
  body: { AMOUNT: '100' },              // extra keys in actual body are ignored
  headers: { 'x-merchant-id': 'M001' },
});

Body matching uses deep partial equality: nested objects are matched partially, NaN is handled correctly via Object.is, Date instances are compared by value, and arrays are never confused with plain objects.

Strict Mode ​

Enable strict mode to fail fast whenever a call is made to an unregistered endpoint — even when a global default is configured. Useful for catching unexpected HTTP calls:

typescript
const adapter = new MockHttpAdapter({ strict: true });
adapter.onEndpoint('/api/payments').mockResolvedValue({ STATUS: 'SUCCESS' });

// This throws immediately — '/api/users' is not registered
await adapter.send(unregisteredRequest);

Reset ​

reset() clears all calls, queues, and default behaviours without invalidating existing onEndpoint() references:

typescript
beforeEach(() => adapter.reset());

Full Example ​

typescript
import { MockHttpAdapter } from '@yildizpay/http-adapter/testing';
import { HttpMethod, NotFoundException } from '@yildizpay/http-adapter';
import { PaymentService } from './payment.service';

describe('PaymentService', () => {
  let adapter: MockHttpAdapter;
  let service: PaymentService;

  beforeEach(() => {
    adapter = new MockHttpAdapter();
    service = new PaymentService(adapter);
  });

  afterEach(() => adapter.reset());

  it('creates a charge and returns the charge ID', async () => {
    adapter.onEndpoint('/v1/charges').mockResolvedValue({ id: 'ch_123', status: 'succeeded' });

    const result = await service.charge({ amount: 1000, currency: 'USD' });

    expect(result.chargeId).toBe('ch_123');
    adapter.assertCalledWith('/v1/charges', {
      method: HttpMethod.POST,
      body: { amount: 1000, currency: 'USD' },
    });
  });

  it('throws when the charge endpoint returns 404', async () => {
    adapter.onEndpoint('/v1/charges').mockRejectedValue(
      new NotFoundException('Charge not found', /* response */ null),
    );

    await expect(service.charge({ amount: 1000, currency: 'USD' }))
      .rejects.toThrow(NotFoundException);
  });
});

Released under the MIT License.