Skip to content

How to troubleshoot common problems

Practical fixes for the most frequent issues encountered when running or configuring utest.


Tests are not found

Symptom: utest reports 0 total and exits 0, even though test files exist.

Cause: utest scans test/unit/*_test.uc by default. Files outside that directory or with a different naming suffix are not discovered.

Fix: Pass the file or directory explicitly:

utest test/my_suite_test.uc
utest test/integration/

Or change the pattern in utest.config.uc:

return { pattern: '*_spec.uc' };

A module import is not being intercepted

Symptom: Inside a test, import * as fs from 'fs' returns the real filesystem module even though a mock is set up with mock.inject() or mock.global.patch().

Cause: The module was not listed in the mocks key of utest.config.uc.

Fix: Add the module to your config file:

// utest.config.uc
return {
    mocks: {
        'fs': null
    }
};

null uses the built-in proxy. To use a custom proxy instead, pass a { proxy: 'path/to/proxy.uc' } object. See Reference: CLI and Configuration for the full syntax.


A worker times out

Symptom: The runner prints worker timed out after 60s for one or more test files, and those files are marked as fatal errors.

Cause: A test file took longer than the configured timeout. This can happen when code under test calls a blocking operation (a real network request, a sleep, a tight loop) that the test did not mock out.

Fix: Identify the blocking call and mock it. For example, if uloop timers are blocking, use the uloop proxy:

// utest.config.uc
return { mocks: { 'uloop': null } };

If the test genuinely needs more time, raise the timeout in utest.config.uc:

return { timeout: 120 };

Tests pass locally but fail in CI

Symptom: All tests pass locally but the same commit fails in CI.

Possible causes and fixes:

Non-deterministic test ordering: By default, utest shuffles tests. The seed is printed in the summary. Reproduce the CI failure locally by passing that seed with -s:

utest -s 1234567890 test/unit

The seed is set only on the command line — there is no seed config key. -s fixes both the test-file shuffle order and the default seed for property tests.

Parallel race condition: Tests that share mutable global state can interfere when run concurrently. Set jobs: 1 in utest.config.uc to confirm the failure is order-dependent:

return { jobs: 1 };

If the failure disappears, the tests share state that needs to be isolated per test (use beforeEach/afterEach to set up and tear down).

Missing mock declaration: The CI environment may have a different real module available (or none at all). Ensure all required modules are declared in mocks so the test never reaches the real implementation.


assert.match reports a mismatch but the values look identical

Symptom: A failing test prints both the expected and actual values, and they appear to be the same string or number.

Cause: The values differ in type. ucode distinguishes "5" (string) from 5 (integer). assert.match uses structural equality, so type mismatches fail even when the printed representations look the same.

Fix: Check the types explicitly. type() returns the type name as a string:

assert.match("int", type(actual));
assert.match(5, actual);

Alternatively, use pred() with an explicit comparison that coerces as intended:

import { assert, pred } from 'utest';
assert.match(pred(x => x == 5), actual, 'expected 5 (any type)');

could not create ... directory error

Symptom: The runner exits immediately with an error such as [utest] error: could not create worker output directory: ... (or ... shim directory: ...).

Cause: utest creates a temporary run directory under /tmp (via mktemp) and writes shims and per-worker output files into it. One of those directories could not be created, usually because /tmp is full, read-only, or the process lacks write permission.

Fix: Check available space and permissions:

df -h /tmp
ls -ld /tmp

On OpenWrt, /tmp is a tmpfs mount sized to half of available RAM. If it is full, clear space or increase the tmpfs size in your system configuration.


No color in the output

Symptom: The detailed reporter output contains no ANSI color codes, making pass/fail lines hard to distinguish at a glance.

Cause: Color is disabled either automatically (when stdout is not a TTY, e.g. in CI or when piped to a file) or explicitly via color: false in the config.

Fix: Check that your utest.config.uc does not set color: false. To force color on unconditionally, set color: true:

return { color: true };

mock.reset() did not clear a global.patch()

Symptom: After calling mock.reset(), a module patched with mock.global.patch() is still intercepting calls — the mock data is still active.

Cause: mock.reset() clears only transient layers created by mock.inject(). It does not touch global state installed by mock.global.patch().

Fix: To remove a specific global patch, call mock.global.unpatch():

mock.global.unpatch('fs');

To roll back to a known checkpoint that includes global state, use mock.snapshot() and mock.restore():

const snap = mock.snapshot();       // save before patching
mock.global.patch('fs', { ... });
// … tests …
mock.restore(snap);                 // removes the global patch and all layers

See How-to: Save and restore mock state for a full walkthrough.


An unrecognised option error

Symptom: utest: Unknown option: -x

Cause: The flag does not exist or is misspelled.

Fix: Run utest -h to see every supported flag, or consult Reference: CLI and Configuration.


Next steps