Test patterns

How to use generated mocks in your unit tests.

Setup: include paths

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

Pattern 1: Basic C function-pointer override

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);
}

Pattern 2: Call counting

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);
}

Pattern 3: Argument verification

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);
}

Pattern 4: Simulating errors

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
}

Pattern 5: Sequential behavior

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);
}

Pattern 6: Restoring default

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;
}

Pattern 7: C++ virtual method override

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);
}

Pattern 8: Test fixture for mock state

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);
}

Pattern 9: Testing your project code (end-to-end)

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);
}

Compilation checklist

StepExample
1. Generate mocksmockware 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 sourcesmock-sdk/source/*.c mock-sdk/source/*.cpp
5. Add project sourcesyour_project/*.cpp
6. Link test framework-l catch2 or include header-only