#knurling #defmt #testing

no-std dev defmt-test

A test harness for embedded devices

7 releases

0.3.0 Nov 10, 2021
0.2.3 May 21, 2021
0.2.2 Apr 29, 2021
0.2.1 Feb 26, 2021
0.1.0 Nov 16, 2020

#858 in Embedded development

Download history 175/week @ 2022-11-27 308/week @ 2022-12-04 178/week @ 2022-12-11 170/week @ 2022-12-18 163/week @ 2022-12-25 205/week @ 2023-01-01 99/week @ 2023-01-08 180/week @ 2023-01-15 345/week @ 2023-01-22 166/week @ 2023-01-29 142/week @ 2023-02-05 123/week @ 2023-02-12 1123/week @ 2023-02-19 152/week @ 2023-02-26 238/week @ 2023-03-05 102/week @ 2023-03-12

1,623 downloads per month
Used in 5 crates


1.5K SLoC


defmt-test is a test harness for embedded devices that lets you write and run unit tests on your device as if you were using the built-in #[test] attribute.

It is compatible with rust-analyzer's ▶ Run Test button, which means you can flash and run your tests straight from VS Code:

demo: clicking the run button above a defmt_test::tests module leads to flashing & test run

For a full list of defmt-test's capabilities, please refer to the documentation below.

Basic usage

Start from the app-template and then run cargo test -p testsuite to run the unit tests:

$ cargo test -p testsuite
0.000000 INFO  (1/2) running `assert_true`...
└─ test::tests::__defmt_test_entry @ tests/test.rs:7
0.000001 INFO  (2/2) running `assert_false`...
└─ test::tests::__defmt_test_entry @ tests/test.rs:7
0.000002 ERROR panicked at 'TODO: write actual tests', testsuite/tests/test.rs:16:9
└─ panic_probe::print_defmt::print @ (..omitted..)
stack backtrace:
   0: HardFaultTrampoline
      <exception entry>
   1: __udf
   2: cortex_m::asm::udf
        at (..omitted..)
   3: rust_begin_unwind
        at (..omitted..)
   4: core::panicking::panic_fmt
        at (..omitted..)
   5: core::panicking::panic
        at (..omitted..)
   6: test::tests::assert_false
        at tests/test.rs:16
   7: main
        at tests/test.rs:7
   8: ResetTrampoline
        at (..omitted..)
   9: Reset
        at (..omitted..)

NOTE unit tests will be executed sequentially

Adding state

An #[init] function can be written within the #[tests] module. This function will be executed before all unit tests and its return value, the test suite state, can be passed to unit tests as an argument.

// state shared across unit tests
struct MyState {
    flag: bool,

mod tests {
    fn init() -> super::MyState {
        // state initial value
        super::MyState {
            flag: true,

    // this unit test doesn't access the state
    fn assert_true() {

    // but this test does
    fn assert_flag(state: &mut super::MyState) {
$ cargo test -p testsuite
0.000000 INFO  (1/2) running `assert_true`...
└─ test::tests::__defmt_test_entry @ tests/test.rs:11
0.000001 INFO  (2/2) running `assert_flag`...
└─ test::tests::__defmt_test_entry @ tests/test.rs:11
0.000002 INFO  all tests passed!
└─ test::tests::__defmt_test_entry @ tests/test.rs:11

Test Outcome

Test functions may either return () and panic on failure, or return any other type that implements the TestOutcome trait, such as Result.

This allows tests to indicate failure via Result, which allows using the ? operator to propagate errors.

Similar to Rust's built-in #[should_panic] attribute, defmt-test supports a #[should_error] attribute, which inverts the meaning of the returned TestOutcome. Err makes the test pass, while Ok/() make it fail.


defmt-test is part of the Knurling project, Ferrous Systems' effort at improving tooling used to develop for embedded systems.

If you think that our work is useful, consider sponsoring it via GitHub Sponsors.


Licensed under either of

at your option.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.


~34K SLoC