Ceedling (Mocking Hardware)
2026-04-17
Edited: 2026-04-18
This is useful for unit tests on functions interacting with hardware. Except we want to mock the hardware out. Ceedling is a unit testing and also a build system. Suppose we want to test functions interacting with a test sensor, let's call it astute_sensor. We start by calling
~ ceedling module:create[astute_sensor]
Generating "astute_sensor"...
mkdir -p ./test/.
mkdir -p ./src/.
mkdir -p ./inc/.
File ./test/./test_astute_sensor.c created
File ./src/./astute_sensor.c created
File ./inc/./astute_sensor.h createdAs you can see, it automatically creates some files for us. You can configure include, test, and source directories through a yaml file, which will be great for stm32 projects which have rather evil project conventions.
:project:
:build_root: project/build/
:release_build: TRUE
:paths:
:test:
- tests/**
:source:
- source/**
:include:
- inc/**Under the hood, CMock is actually used to mock function values. What is going to happen is that you will mock out calls which interact with the hardware, such as any of STM32's HAL functions. Before that you need to tell CMock to mock these functions. So let's create a mock_hal.h file
#ifndef MOCK_HAL_H
#define MOCK_HAL_H
// Put all your HAL functions here
bool i2c_read(uint8_t address);
//...
#endif // MOCK_HAL_HNote that if you use something like STM32's HAL library or any other vendor library, you can also mock those directly. The rest of this text will assume you are using STM32's HAL, as that is my main usecase. The above is just an example of mocking your own HAL library. Now suppose we implement our functions in astute_sensor.c
#include "astute_sensor.h"
#include "main.h" // or maybe more accurately #include "stm32h7xx_hal.h"
#include <stdint.h>
// ...
float astute_sensor_read(struct *astute_sensor s)
{
uint8_t value = 0;
HAL_SPI_Receive(&s->handle, &, s->data_size,);
// Do some weird mapping
return (value * 0.2) + 0.3;
}
// ...Then we head on to write our tests in test_astute_sensor.c. For this we have to tell CMock to mock the functions in addition to telling it what functions we are going to call and what values it should return.
// Tell CMock to mock HAL functions
#include "mock_stm32h7xx_hal.h" // #include "mock_main.h" might work, not sure
void test_astute_sensor_read_value(void)
{
// Mock parameters
struct astute_sensor sensor = {
.handle = NULL, // don't care
.data_size = 1, // don't care
};
float expected = 5.1f;
float tolerance = 0.1f;
uint8_t value = 24;
// Tell CMock what function we want to mock as well as what to return
// when we do mock it.
HAL_SPI_Receive_ExpectAndReturn(&s->handle, NULL, s->data_size,);
// Tell CMock to place 4 into the value for the read buffer, that way the 'value' variable inside
// 'astute_sensor_read' will be set to 4;
HAL_SPI_Receive_pData(&);
// Check the value, make sure to mock every HAL call!
float actual = astute_temperature_read(&);
TEST_ASSERT_FLOAT_WITHIN(,,);
}Check the CMock documentation for more mocks. Run ceedling test to run all tests.
Reference
https://www.electronvector.com/blog/mocking-embedded-hardware-interfaces-with-ceedling-and-cmock