test
import {describe, test, assert} from 'mikro/test'A lightweight test framework for running tests directly on the device.
Writing tests
import {describe, test, assert} from 'mikro/test'
describe('math', () => {
test('addition', () => {
assert.equal(1 + 1, 2)
})
test('async', async () => {
const result = await someAsyncOperation()
assert.ok(result)
})
})Run with mikro test:
mikro testdescribe(name, fn)
describe(name: string, fn: () => void): voidGroup related tests into a suite. Suites can contain tests, beforeAll, and afterAll hooks. Garbage collection runs between suites to manage heap pressure.
describe.skip(name, fn)
describe.skip(name: string, fn: () => void): voidSkip all tests in a suite. They will be reported as skipped in the results.
describe.only(name, fn)
describe.only(name: string, fn: () => void): voidFocus a suite. When any .only is present in a file, all non-.only suites and tests in that file are skipped. Focus is per-file only; other test files in the same mikro test run still execute normally. Useful for narrowing down a failing suite during debugging.
describe.only('the one I care about', () => {
test('runs', () => {})
})
describe('everything else', () => {
test('is skipped', () => {})
})describe.todo(name, fn)
describe.todo(name: string, fn: () => void): voidMark an entire suite as todo. Every test in it is reported as todo, regardless of individual flags. Useful for sketching out a feature you plan to implement.
describe.todo('bluetooth driver', () => {
test('connects', () => {})
test('disconnects', () => {})
})describe.fixme(name, fn)
describe.fixme(name: string, fn: () => void): voidSame as describe.skip but signals that the suite is broken and needs to be fixed. Reported as skipped.
describe.skipIf(condition)
describe.skipIf(condition: unknown): (name: string, fn: () => void) => voidSkip the suite when condition is truthy.
describe.skipIf(env.get('MIKRO_ENV') === 'test')('prod only', () => {
test('production check', () => {
/* ... */
})
})describe.runIf(condition)
describe.runIf(condition: unknown): (name: string, fn: () => void) => voidOnly run the suite when condition is truthy. This is the inverse of skipIf.
const hasWifi = env.get('WIFI_SSID') && env.get('WIFI_PASSPHRASE')
describe.runIf(hasWifi)('wifi', () => {
test('connect', async () => {
/* ... */
})
})test(name, fn)
test(name: string, fn: () => void | Promise<void>): voidDefine a test. The function can be sync or async. Each test has a 10 second timeout.
test.skip(name, fn)
test.skip(name: string, fn: () => void | Promise<void>): voidSkip a test. It will be reported as skipped in the results.
describe('feature', () => {
test.skip('not ready yet', () => {
// this won't run
})
})test.only(name, fn)
test.only(name: string, fn: () => void | Promise<void>): voidFocus a single test. When any .only is present in a file, all non-.only tests in that file are skipped. Within a suite, if some tests are .only and others are not, only the .only tests run. Focus is per-file only; other test files in the same mikro test run still execute normally. Pass a path pattern to mikro test to narrow the run itself.
describe('math', () => {
test.only('this one', () => {
assert.equal(1 + 1, 2)
})
test('others are skipped', () => {})
})test.fixme(name, fn)
test.fixme(name: string, fn: () => void | Promise<void>): voidSame as test.skip but signals that the test is broken and needs to be fixed. Reported as skipped.
test.skipIf(condition)
test.skipIf(condition: unknown): (name: string, fn: () => void | Promise<void>) => voidSkip the test when condition is truthy.
describe('feature', () => {
test.skipIf(env.get('MIKRO_ENV') === 'test')('prod only', () => {
// skipped during test runs
})
})test.runIf(condition)
test.runIf(condition: unknown): (name: string, fn: () => void | Promise<void>) => voidOnly run the test when condition is truthy. This is the inverse of skipIf.
describe('hardware', () => {
test.runIf(env.get('HAS_SENSOR'))('read sensor', () => {
// only runs when HAS_SENSOR is set
})
})test.todo(name)
test.todo(name: string): voidRegister a placeholder test with no function body. Todo tests are not executed and show in blue in the output.
describe('feature', () => {
test.todo('handle reconnect after deep sleep')
})test.each(cases)
test.each<T>(cases: T[]): (name: string, fn: (value: T, index: number) => void | Promise<void>) => voidRun a test once for each case. Name interpolation: %s (string), %# (index), %o (JSON).
describe('math', () => {
test.each([1, 4, 9])('sqrt of %s', (n) => {
assert.equal(Math.sqrt(n) % 1, 0)
})
})Also available on describe:
describe.each([9600, 115200])('uart at %s baud', (baud) => {
test('sends data', () => {
/* ... */
})
})Composes with skipIf and runIf:
test.skipIf(isCI).each([1, 2, 3])('hardware pin %s', (pin) => {
/* ... */
})beforeAll(fn) / afterAll(fn)
beforeAll(fn: () => void | Promise<void>): void
afterAll(fn: () => void | Promise<void>): voidRun setup/teardown once per suite. If beforeAll throws, all tests in the suite are skipped. afterAll errors are non-fatal.
describe('storage', () => {
afterAll(() => {
nvsStorage.clear().orPanic('nvs clear failed')
})
test('write and read', () => {
const val = nvsStorage.createValue('key')
val.set('hello')
assert.equal(val.get(), 'hello')
})
})beforeEach(fn) / afterEach(fn)
beforeEach(fn: () => void | Promise<void>): void
afterEach(fn: () => void | Promise<void>): voidRun setup/teardown before and after each test. beforeEach runs inside the test timeout; if it throws, the test fails. afterEach always runs, even if the test failed; errors are non-fatal.
describe('storage', () => {
afterEach(() => {
nvsStorage.delete('test-key')
})
test('write', () => {
nvsStorage.createValue('test-key').set('hello')
})
test('independent read', () => {
assert.equal(nvsStorage.createValue('test-key').get(), undefined)
})
})assert
assert.equal(actual, expected)
Strict equality using Object.is.
assert.equal(1 + 1, 2)
assert.equal('hello', 'hello')assert.notEqual(actual, unexpected)
Strict inequality.
assert.truthy(value, message?)
Assert value is truthy.
assert.truthy(true)
assert.truthy('non-empty string')
assert.truthy(1 > 0, 'expected positive')assert.deepEqual(actual, expected)
Deep equality via JSON.stringify comparison.
assert.deepEqual({a: 1, b: 2}, {a: 1, b: 2})
assert.deepEqual([1, 2, 3], [1, 2, 3])assert.throws(fn, message?)
Assert that a function throws. Returns the caught error.
const err = assert.throws(() => {
throw new Error('boom')
})
assert.equal(err.message, 'boom')assert.rejects(fn, message?)
Assert that an async function rejects. Returns the caught error.
const err = await assert.rejects(async () => {
throw new Error('async boom')
})assert.type(value, expected)
Assert typeof value === expected.
assert.type('hello', 'string')
assert.type(42, 'number')
assert.type(true, 'boolean')assert.instance(value, constructor)
Assert value instanceof constructor.
assert.ok(result)
Assert a Result is ok. Narrows result to OkResult, so result.value is typed and accessible afterwards.
const encoded = encode({temp: 22.5})
assert.ok(encoded)
const bytes = encoded.value // typed as Uint8Arrayassert.err(result)
Assert a Result is an error. Narrows result to ErrResult, so result.error is typed and accessible afterwards.
Environment
When running under mikro test, the environment variable MIKRO_ENV is set to "test". Use this for conditional behavior:
if (env.get('MIKRO_ENV') === 'test') {
// test-specific setup
}