Node.js Test Runner
SkillDev toolsUnit testing patterns for astro-blocks using the built-in Node.js test runner. Covers node:test imports, node:assert/strict assertions, withTempProject temp-dir isolation, env-var save/restore, async test syntax, and subset run commands. Use when writing unit tests, running node --test, using node:test or assert/strict, isolating tests with withTempProject, or running a subset of tests with --test-name-pattern.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Node.js Test Runner skill
What this skill tells your AI
The instructions your AI receives, as published by nauelg/astro-blocks in .agents/skills/node-test-runner/SKILL.md and read by ahel’s review.
Overview
This skill covers unit testing in astro-blocks using the built-in node:test runner and node:assert/strict assertion module. All 16 test files under tests/ follow this pattern — no jest, no vitest, no extra frameworks.
Tests import from ../dist/... (the compiled output), so a build step is always required before running. End-to-end browser tests are not in scope here — use the playwright-best-practices skill for those.
When to Use
- Writing a new
tests/*.test.jsfile for an API handler, utility, or contract function. - Debugging a failing or flaky async test.
- Running only one test file or one named test during development.
- Looking up the
withTempProjectisolation pattern (used in every file that touchesdata/). - Looking up env-var save/restore idiom (
ASTRO_BLOCKS_PROJECT_ROOT).
Running Tests
Full suite
npm test
# expands to: npm run build && node --test tests/*.test.js
Defined in package.json:48. Build is mandatory — tests import from ../dist/.
Single file
node --test tests/configs-handlers.test.js
Subset by name (regex)
node --test --test-name-pattern="CRUD" tests/*.test.js
Watch mode (Node ≥ 22)
node --test --watch tests/cache.test.js
Test Structure
Imports
Every test file starts with the same two imports (see tests/cache.test.js:6-7, tests/contract.test.js:1-2):
import test from 'node:test';
import assert from 'node:assert/strict';
Import additional node modules (fs, os, path) as needed. Import the module under test from ../dist/....
Top-level test (flat)
test('getPageCachePath normalizes homepage', () => {
assert.equal(getPageCachePath({ slug: '/' }), '/');
});
Async test
test('ensureDefaultFiles creates configs.json', async () => {
await withTempProject(async (tempRoot) => {
const raw = await fs.readFile(path.join(tempRoot, 'data', 'configs.json'), 'utf-8');
assert.deepEqual(JSON.parse(raw), { configs: [] });
});
});
Patterns
withTempProject — temp-dir isolation
Every test that reads or writes data/ files must use withTempProject. The helper is defined inline in each test file (not a shared import). Copy this verbatim:
// tests/configs-handlers.test.js:20-38
async function withTempProject(fn) {
const previousRoot = process.env.ASTRO_BLOCKS_PROJECT_ROOT;
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'astro-blocks-<suite>-'));
process.env.ASTRO_BLOCKS_PROJECT_ROOT = tempRoot;
await ensureDefaultFiles();
try {
await fn(tempRoot);
} finally {
if (previousRoot === undefined) {
delete process.env.ASTRO_BLOCKS_PROJECT_ROOT;
} else {
process.env.ASTRO_BLOCKS_PROJECT_ROOT = previousRoot;
}
await fs.rm(tempRoot, { recursive: true, force: true });
}
}
Replace <suite> with a short name matching the test file (e.g. astro-blocks-configs-, astro-blocks-redirects-).
Usage:
test('handlers support CRUD', async () => {
await withTempProject(async (tempRoot) => {
// tempRoot is the isolated project root for this test
});
});
Env-var save/restore (without ensureDefaultFiles)
For simpler tests that only need a custom project root without calling ensureDefaultFiles, use the inline try/finally pattern (see tests/get-config.test.js:15-47):
// tests/get-config.test.js:15-16
const previousRoot = process.env.ASTRO_BLOCKS_PROJECT_ROOT;
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'astro-blocks-get-config-'));
try {
process.env.ASTRO_BLOCKS_PROJECT_ROOT = tempRoot;
// ... test body
} finally {
if (previousRoot === undefined) delete process.env.ASTRO_BLOCKS_PROJECT_ROOT;
else process.env.ASTRO_BLOCKS_PROJECT_ROOT = previousRoot;
await fs.rm(tempRoot, { recursive: true, force: true });
}
Key rule: always check === undefined before deciding whether to delete or restore the env var. Never blindly assign undefined.
node:assert/strict — assertion styles
// Strict equality (===)
assert.equal(response.status, 200);
// Deep structural equality
assert.deepEqual(JSON.parse(raw), { configs: [] });
// Regex match on a string
// tests/contract.test.js:45
assert.match(validateBlocks(schemaMap, [{ type: 'Hero', props: {} }])?.message || '', /campo "Title" es obligatorio/);
// Truthy (array membership, booleans)
assert.ok(tags.includes('astro-blocks'));
// Async rejection — assert a promise rejects
await assert.rejects(
async () => someAsyncFn(),
{ message: /expected error pattern/ }
);
Importing from dist/
Tests always import from the compiled output, never from src/:
// tests/cache.test.js:9-18
import { CACHE_PATHS, getGlobalCachePaths } from '../dist/utils/cache.js';
// tests/configs-handlers.test.js:12-18
import { ensureDefaultFiles, loadConfigs } from '../dist/api/data.js';
import { handleDeleteConfig, handleGetConfigs } from '../dist/api/handlers.js';
If a new module is added under src/, run npm run build before the test file can import from ../dist/.
Compact Rules
- Run the full suite with
npm test(builds first); never runnode --testwithout a prior build. - Import
testfromnode:testandassertfromnode:assert/strict— no other test frameworks. - Use
withTempProject(fn)for every test that touchesdata/files; define it inline per test file. - Save env vars before mutation (
const prev = process.env.X); restore infinally; usedeletewhenprev === undefined. - Import modules under test from
../dist/..., not../src/.... - Use
assert.equalfor scalar equality,assert.deepEqualfor objects/arrays,assert.matchfor regex,assert.rejectsfor async errors. - Run a subset with
--test-name-pattern=<regex>or a single file withnode --test tests/<file>.test.js.
Signals
- GitHub stars
- 27
- Forks
- 1
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
node-test-runner- Source
- github.com/nauelg/astro-blocks