Skip to content
Chiplab logo
Chiplab logo
Back to Now

native_sim is not a unit test. Zephyr had to write that down.

Both targets produce a Linux executable. Both sit under Twister. Both let you debug with gdb. So people treat native_sim and BOARD=unit_testing as two spellings of "run Zephyr on the host."

Zephyr's own test-framework page opens a subsection by admitting the two are easy to confuse, then says: they are fundamentally different.1

When a project has to write that sentence, the confusion is not theoretical. It is the default.

Two host binaries, two machines

unit_testing is a pseudo-board. arch: unit. It is not hardware and it is not a simulated SoC. Twister selects it when the scenario sets type: unit. The build links the files you put on the testbinary target with the Ztest harness, using the host toolchain. The kernel is not in the image. There is no boot, no scheduler, no devicetree init, no driver model. You call the function. If it needed a kernel API, you stub it.1

native_sim builds the complete Zephyr OS — kernel, devicetree, Kconfig, drivers, subsystems — into a host binary that boots and runs like a Zephyr image, compiled for the host instead of a target SoC. Tests that use it do not set type: unit.12

Reach for native_sim to exercise code in a running Zephyr system. Reach for unit_testing to test an isolated module without pulling in the kernel.1

Diagram contrasting Zephyr unit_testing, which omits the kernel, with native_sim, which boots a full Zephyr image on the host

What native_sim is honest about

The board page is blunt. native_sim "does not intend to simulate any particular HW." It offers a few peripherals — Ethernet, display, UART — so application code that needs those surfaces can run. It does not model a chip. Code is compiled for the host, typically x86. There is no I/O or MMU emulation; a hardcoded address segfaults.3 Time is simulated and, by default, decoupled from wall time. The kernel thinks time is whatever the models say.2

The POSIX-architecture writeup underneath it is even blunter. The port assumes code executes in zero simulated time. It is not for debugging hardware/software races or missed programming deadlines. It does not replace an instruction-set simulator, a development board, or QEMU. It complements them.3

That last sentence is the one CI setups skip. They see a green Twister run on native_sim and file it under "we tested on a board." They tested on a host program that contains a Zephyr kernel. That is a real test. It is not a board.

native_posix is the old name. Same idea.2

Why the mix-up keeps happening

Twister's console output reports how a test ran: qemu, native_sim, or build-only. The status line does not say whether the kernel was in the binary.4 If you live in the YAML and not in the CMake, type: unit is a small key. Miss it and you built a different kind of program than you think.

The 2018 argument is still the argument. One engineer wrote that ztest is for unit tests and native_posix is for system tests, and that you cannot mock the world away under native_posix because the world is the point. Another wanted native_posix to replace the mock layer so they would not have to maintain empty headers. The thread did not resolve into one tool. It resolved into two jobs.5

Zephyr later grew FFF mocks inside Ztest, which makes the unit-testing board more pleasant and does not make native_sim a unit-test target. Different layer.

I have made this split before. Firmware CI that ends at the linker is the unit-testing failure mode: you compiled something, you did not run the system. Simulation versus HIL is the native_sim failure mode: you ran a system, it was not the silicon. Both belong in CI. They answer different tickets.

Ladder placing Zephyr unit_testing, native_sim, and target simulation or hardware as three different test rungs

What you can claim after a native_sim pass

You can claim the kernel scheduled, the app reached the state you asserted, the subsystem you enabled initialized, and a host-backed UART or Ethernet path did what that backend does. You can debug it with host tools, ASan, UBSan, coverage. Those are large claims. They are why the target exists.2

You cannot claim the STM32 timer did the thing, the nRF radio did the thing, or the image you will flash did the thing. That image was not built. The ABI is the host ABI — 32-bit native_sim exists specifically because most MCUs are ILP32 and 64-bit host longs hide bugs.2 Even the 32-bit target is still host code running host instructions.

If the behavior you care about is "does this module return the right value given these stubs," use unit_testing. If it is "does this Zephyr app boot and pass its integration tests without a board," use native_sim. If it is "does this driver speak this chip," you have left both pages. That is what simulation can't catch when people pretend otherwise.

The docs already picked the words. Fundamentally different. Use them.

Sources

Footnotes

  1. Zephyr Project, "Test Framework," Zephyr documentation, accessed 2026-08-14. https://docs.zephyrproject.org/latest/develop/test/ztest.html 2 3 4

  2. Zephyr Project, "Native simulator - native_sim," Zephyr documentation, accessed 2026-08-14. https://docs.zephyrproject.org/latest/boards/native/native_sim/doc/index.html 2 3 4 5

  3. Zephyr Project, "The POSIX architecture," Zephyr documentation, accessed 2026-08-14. https://docs.zephyrproject.org/latest/boards/native/doc/arch_soc.html 2

  4. Zephyr Project, "Test Runner (Twister)," Zephyr documentation, accessed 2026-08-14. https://docs.zephyrproject.org/latest/develop/twister/index.html

  5. Zephyr Project, "native_posix supersedes ztest mocking," GitHub issue 7316, 2018-05-02. https://github.com/zephyrproject-rtos/zephyr/issues/7316

Daniel Frassinelli
Published Aug 14, 2026