Skip to content

How to mock HTTP requests with uclient

Declare uclient: null in your utest.config.uc mocks table. See How-to: Mock a module with mock.inject() for the setup steps.

For the exact callback firing order and full data field reference, see Reference: Proxy Data Models — uclient.


Seed URL-keyed response data

Each key in the data map is a full request URL. The value describes the response.

For a successful response, supply { status, headers, body }:

import { describe, it, mock, assert, falsy } from 'utest';

describe('uclient mock', () => {
    it('fires callbacks for a successful response', () => {
        const url = 'http://api.example.com/items';
        mock.inject('uclient', {
            data: { [url]: { status: 200, headers: { 'content-type': 'application/json' }, body: '[]' } }
        }, (m_uclient) => {
            let events = [];
            let u = m_uclient.new(url, null, {
                header_done: () => push(events, 'header_done'),
                data_read:   () => push(events, 'data_read'),
                data_eof:    () => push(events, 'data_eof')
            });
            u.request('GET', {});
            assert.match(['header_done', 'data_read', 'data_eof'], events);
        });
    });
});

For an error response, supply { error: code }:

mock.inject('uclient', {
    data: { 'http://api.example.com/broken': { error: 'connection_refused' } }
}, (m_uclient) => {
    let got_error = null;
    let u = m_uclient.new('http://api.example.com/broken', null, {
        error: (conn, code) => { got_error = code; }
    });
    u.request('GET', {});
    assert.match('connection_refused', got_error);
});

Read status and headers inside header_done

The connection object passed to each callback exposes status() and get_headers(). Read them inside header_done to inspect the response metadata:

const url = 'http://api.example.com/status';
mock.inject('uclient', {
    data: { [url]: { status: 201, headers: { 'x-custom': 'yes' }, body: '' } }
}, (m_uclient) => {
    let got_status = null;
    let got_headers = null;
    let u = m_uclient.new(url, null, {
        header_done: (conn) => {
            got_status = conn.status().status;
            got_headers = conn.get_headers();
        },
        data_read: () => {},
        data_eof:  () => {}
    });
    u.request('GET', {});
    assert.match(201, got_status);
    assert.match('yes', got_headers['x-custom']);
});

read() loop pattern

Inside data_read, call conn.read() in a loop until it returns null. The mock returns the body string on the first call and null on all subsequent calls, matching the real uclient read loop:

const url = 'http://api.example.com/body';
mock.inject('uclient', {
    data: { [url]: { status: 200, headers: {}, body: 'hello world' } }
}, (m_uclient) => {
    let chunks = [];
    let u = m_uclient.new(url, null, {
        data_read: (conn) => {
            let chunk;
            while ((chunk = conn.read()) != null) push(chunks, chunk);
        },
        header_done: () => {},
        data_eof:    () => {}
    });
    u.request('GET', {});
    assert.match(['hello world'], chunks);
});

null body skips data_read

Set body: null to model a response with no body (such as HTTP 204). data_read is not called:

const url = 'http://api.example.com/no-content';
mock.inject('uclient', {
    data: { [url]: { status: 204, headers: {}, body: null } }
}, (m_uclient) => {
    let data_read_fired = false;
    let u = m_uclient.new(url, null, {
        header_done: () => {},
        data_read:   () => { data_read_fired = true; },
        data_eof:    () => {}
    });
    u.request('GET', {});
    assert.match(falsy(), data_read_fired);
});

Simulate connect() failure with behavior override

Supply behavior.connect to make connection setup fail. Code that checks the return value of connect() before proceeding can be exercised without triggering any request:

mock.inject('uclient', {
    behavior: { connect: () => false }
}, (m_uclient) => {
    let u = m_uclient.new('http://example.com/', null, {});
    assert.match(false, u.connect());
});

Next steps