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.

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 know | Use |
|---|---|
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.

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
-
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 ↩
-
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 ↩
-
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 ↩
-
ThrowTheSwitch, "Mocking nested headers (or something like it)," Ceedling issue 171, 2017-05-23. https://github.com/ThrowTheSwitch/Ceedling/issues/171 ↩
-
ThrowTheSwitch, "Creating Tests," Ceedling issue 503, 2020-07-03. https://github.com/ThrowTheSwitch/Ceedling/issues/503 ↩ ↩2