How to use generated mocks in your unit tests.
Compile your tests with the mock include directory before any real SDK paths so the mock headers take precedence.
# Using g++/clang++
g++ -I mock-sdk/include \ # mock headers first
-I /path/to/your/project \ # your project headers
mock-sdk/source/*.c \ # mock implementations
your_project/*.cpp \ # code under test
your_test.cpp \ # your tests
-o test_runner
Include mockware/fakes.h to get access to the _mock function pointers. Assign your stub to replace the default implementation.
// test_gpio.cpp
#include <cassert>
#include "driver/gpio.h" // mock header (from mock-sdk/include)
#include "mockware/fakes.h" // extern function pointer declarations
void test_gpio_set_level_success() {
// Replace with a lambda that returns success
gpio_set_level_mock = [](int pin, int level) -> esp_err_t {
return ESP_OK;
};
esp_err_t result = gpio_set_level(1, 1);
assert(result == ESP_OK);
}
Use a lambda with a captured counter to verify a function is called the expected number of times.
void test_called_exactly_once() {
int call_count = 0;
gpio_set_level_mock = [&call_count](int pin, int level) -> esp_err_t {
call_count++;
return ESP_OK;
};
gpio_set_level(1, 1);
assert(call_count == 1);
gpio_set_level(2, 0);
assert(call_count == 2);
}
Capture and verify the arguments passed to the mock function.
void test_args_are_passed_correctly() {
int captured_pin = -1;
int captured_level = -1;
gpio_set_level_mock = [&](int pin, int level) -> esp_err_t {
captured_pin = pin;
captured_level = level;
return ESP_FAIL;
};
gpio_set_level(5, 1);
assert(captured_pin == 5);
assert(captured_level == 1);
}
Return error codes from the mock to test error handling paths in your code.
void test_handles_nvs_init_failure() {
// Simulate NVS init failing with no free pages
nvs_flash_init_mock = []() -> esp_err_t {
return ESP_ERR_NVS_NO_FREE_PAGES;
};
nvs_flash_erase_mock = []() -> esp_err_t {
return ESP_OK;
};
// Test that your code handles the erase+retry logic
esp_err_t result = NvsStorage::init();
assert(result == ESP_OK); // should recover via erase+retry
}
Return different values on successive calls to simulate state changes.
void test_sequential_calls() {
int call = 0;
esp_timer_get_time_mock = [&call]() -> int64_t {
return ++call * 1000;
};
assert(Utils::millis() == 1000);
assert(Utils::millis() == 2000);
assert(Utils::millis() == 3000);
}
Each mock has a _default function. Save and restore the mock pointer around a test.
void test_restore_default() {
// Save the default
auto saved = gpio_set_level_mock;
// Override for this test
gpio_set_level_mock = [](int, int) -> esp_err_t { return ESP_FAIL; };
assert(gpio_set_level(1, 1) == ESP_FAIL);
// Restore
gpio_set_level_mock = saved;
assert(gpio_set_level(1, 1) == ESP_OK); // back to default
}
Or use func_default directly (it's declared as static in the source, so you need the function pointer address via extern):
// In your test file, declare the default
extern int gpio_get_level_default(int pin);
void test_restore() {
gpio_get_level_mock = gpio_get_level_default;
}
For C++ classes, inherit from the generated class and override virtual methods.
#include "drivers/sensor.hpp"
#include <catch2/catch_test_macros.hpp>
// Define a mock subclass
struct MockSensor : public drivers::TemperatureSensor {
float read() override { return 42.0f; }
sensor_err_t calibrate() override {
calibrate_called_ = true;
return 0;
}
bool calibrate_called_ = false;
};
TEST_CASE("mock sensor with virtual override") {
MockSensor sensor;
CHECK(sensor.read() == 42.0f);
sensor.calibrate();
CHECK(sensor.calibrate_called_);
}
TEST_CASE("default implementation") {
// Using the real class with default virtual impl
drivers::TemperatureSensor sensor;
CHECK(sensor.read() == 25.0f); // auto-generated default
CHECK(sensor.calibrate() == 0);
}
Use setUp/tearDown to reset all mocks between tests.
struct GpioTestFixture {
GpioTestFixture() {
// Save defaults
gpio_set_level_mock = [](int pin, int level) -> esp_err_t {
last_pin = pin;
last_level = level;
return ESP_OK;
};
}
~GpioTestFixture() {
// Mocks are automatically destroyed with the fixture
}
static int last_pin;
static int last_level;
};
int GpioTestFixture::last_pin = -1;
int GpioTestFixture::last_level = -1;
TEST_CASE_METHOD(GpioTestFixture, "gpio with fixture") {
gpio_set_level(3, 1);
CHECK(last_pin == 3);
CHECK(last_level == 1);
}
The most powerful pattern: compile your actual project source files against the mock SDK and test real logic with mocked hardware dependencies.
// test_wifi_manager.cpp โ tests real project code with mocked ESP-IDF
#include <cassert>
#include "wifi_manager.h" // your project code
#include "mockware/fakes.h" // mock access
void test_wifi_start_calls_esp_wifi_start() {
bool wifi_started = false;
// Mock all dependencies
nvs_flash_init_mock = []() -> esp_err_t { return ESP_OK; };
esp_event_loop_create_default_mock = []() -> esp_err_t { return ESP_OK; };
esp_netif_init_mock = []() -> esp_err_t { return ESP_OK; };
esp_wifi_init_mock = [](const wifi_init_config_t*) -> esp_err_t { return ESP_OK; };
esp_wifi_set_mode_mock = [](wifi_mode_t) -> esp_err_t { return ESP_OK; };
esp_wifi_start_mock = [&wifi_started]() -> esp_err_t {
wifi_started = true;
return ESP_OK;
};
esp_wifi_connect_mock = []() -> esp_err_t { return ESP_OK; };
// Call your real project code
WifiService::init();
WifiService::start();
assert(wifi_started);
}
| Step | Example |
|---|---|
| 1. Generate mocks | mockware generate template.yml -o mock-sdk |
| 2. Add mock include | -I mock-sdk/include |
| 3. Add project include | -I /path/to/your/project |
| 4. Add mock sources | mock-sdk/source/*.c mock-sdk/source/*.cpp |
| 5. Add project sources | your_project/*.cpp |
| 6. Link test framework | -l catch2 or include header-only |