Skip to content
Chiplab logo
Chiplab logo
Back to Now

CMock is not a simulator. The STM32 HAL keeps proving it.

Every few years someone tries to mock stm32xxxx_hal_adc_ex.h, CMock chokes on a type defined three includes away, and a thread appears that is really about a different question.

The question is not "how do I get Ceedling to parse this header." The question is what you thought the mock was going to tell you.

The October 2025 version

A developer using Ceedling wants to unit-test a file that calls into stm32h7xx_hal_adc_ex.c. The post notes that ST's docs tell you to include the umbrella stm32h7xx_hal.h. The test includes mock_stm32h7xx_hal_adc_ex.h instead. GCC replies: unknown type name HAL_StatusTypeDef. The type lives in stm32h7xx_hal_def.h. The include chain breaks the moment CMock generates a mock from the inner header.1

The fix that unblocked the build was one extra include in project.yml. Tell CMock to pull in the umbrella header. The test then compiles. That is a real fix for a real parser problem. It is not a simulation of an ADC.

The same pain showed up in a January 2025 question about mocking HAL_UART_Receive, and in December 2024 as a request for an "optimal" STM32 Ceedling config whose interesting lines are a pile of :includes: pointing at stm32l4xx_hal.h.23 ThrowTheSwitch's own tracker has the 2017 nested-header issue and the 2020 "unknown type name FOO_Type" issue. Maintainers keep giving the same advice: CMock will not walk the chain for you; vendor HALs were not written to be mocked; make a thin header of the functions you actually call, or stop testing at this layer.45

Eight years. Same HAL. Same tool. Same surprise.

Diagram of STM32 HAL header chains breaking CMock when a nested header is mocked without its type dependencies

Mock, fake, stub, simulator

Same split as MIL / SIL / PIL / HIL, at the function call.

A stub returns a canned value so the caller can proceed. A fake is a small working stand-in — an in-memory flash, a loopback UART. A mock is a stub plus a script: you declare that HAL_ADCEx_CalibrationStart will be called with these arguments and will return HAL_OK, and the test fails if the call does not happen. CMock is a mock generator. It is good at that script.

A simulator executes the compiled target binary against models of a core and, if you are lucky, some peripherals. Renode, QEMU, a vendor ISS. Different artifact. Different question. I wrote down what simulation still cannot catch and which of those checks belong in CI.

You want to knowUse
Did my module call HAL_ADCEx_CalibrationStart with this handle?Mock
Can my module tolerate HAL_ERROR from that call?Mock
Does my ring buffer do the right thing when the ISR posts a byte?Fake the ISR entry, or stub the HAL receive
Does this HAL init sequence actually start the ADC on this STM32?Simulator or silicon
Does the calibration complete in the time the errata says?Silicon

CMock will happily let you write the last two tests. They will pass. They have not asked the chip anything. They asked your mock whether you called your mock.

Why the HAL is a bad mock target

ST's HAL is an umbrella include, a forest of _ex files, types that live in neighbors, and a lot of #ifdef branches that CMock does not handle like the configured target build. The Ceedling maintainers say it plainly: the STM libraries are really hard for CMock, mostly because of those ifdefs, and the options are a hand-written API header, a configured preprocessor, or a thin wrapper you own.5

That is not incompetence in the test tool. It is the HAL doing what a HAL is for — presenting a huge, configurable C API to application code — colliding with what a mock needs, which is a small, closed surface.

The useful move is the one ThrowTheSwitch has recommended since the nested-header thread: do not mock the vendor tree. Mock your wrapper. adc_start_calibration() is one function. It is yours. The test names the contract you care about. The HAL stays an implementation detail of the target build.

If you skip the wrapper and mock HAL_* directly, you are writing tests that break when Cube regenerates a header, and that still say nothing about the register write.

Diagram contrasting a CMock STM32 HAL unit test that verifies a function call with a simulator or board test that exercises the ADC

Pick the judge that can refute the claim

If the claim is "this module requests calibration before it reads," a mock is the right judge. Fast, deterministic, no board. If the claim is "this Nucleo starts the ADC and the values are not garbage," you need a model of that ADC or the Nucleo.

Most firmware bugs that waste an afternoon are the second kind. Most firmware unit tests that are easy to write are the first kind. Both are worth having. Calling the first one "we mocked the hardware" is how you skip the second and still feel covered.

The October thread got the build green. That is the start of a unit test, not the end of a hardware test.

Sources

Footnotes

  1. Stack Overflow, "Mocking nested functions in a Hardware Abstraction Layer," 2025-10-05. https://stackoverflow.com/questions/79783009/mocking-nested-functions-in-a-hardware-abstraction-layer

  2. Stack Overflow, "Mocking/Stubbing HAL_UART_Receive() for the STM32," 2025-01-15. https://stackoverflow.com/questions/79358148/mocking-stubbing-hal-uart-receive-for-the-stm32

  3. Stack Overflow, "How do I create an optimal, customizable Ceedling configuration for STM32 unit testing?," 2024-12-18. https://stackoverflow.com/questions/79292106/how-do-i-create-an-optimal-customizable-ceedling-configuration-for-stm32-unit-t

  4. ThrowTheSwitch, "Mocking nested headers (or something like it)," Ceedling issue 171, 2017-05-23. https://github.com/ThrowTheSwitch/Ceedling/issues/171

  5. ThrowTheSwitch, "Creating Tests," Ceedling issue 503, 2020-07-03. https://github.com/ThrowTheSwitch/Ceedling/issues/503 2

Daniel Frassinelli
Published Aug 14, 2026