Unit testing ESP-IDF components with GoogleTest (host-based)
Source: Dev.to
Introduction
I wanted a way to test ESP‑IDF component logic without flashing the board each time.
ESP‑IDF can build for a Linux target, allowing tests to run directly on the host machine.
This guide shows how to set that up using GoogleTest as the test framework. The example repository is aluiziotomazelli/gtest‑esp‑idf (chapter 01_basic_test).
Prerequisites
- ESP‑IDF 5.x installed and sourced
- Linux machine or WSL2
- System packages required by the ESP‑IDF Linux target
sudo apt install libbsd0 libbsd-dev
If you use the official ESP‑IDF Docker container (idf-env:latest), the packages are already present.
Note: Ruby is only needed for CMock‑based IDF mocks, which are covered in later chapters.
Repository layout
01_basic_test/
├── CMakeLists.txt
├── include/
│ ├── i_sum.hpp # Interface (abstract base class)
│ └── sum.hpp # Concrete class header
├── src/
│ └── sum.cpp # Production code
├── host_test/
│ ├── gtest/ # GTest wrapper component (CMake only)
│ └── test_sum/ # Test project for the Sum class
└── test_apps/ # Hardware verification (not used for host tests)
- src/ and include/ contain production code.
- host_test/ contains all host‑side test code; the two directories never mix.
i_sum.hpp
Defines an abstract base class for Sum. It isn’t strictly required for this simple example, but it illustrates a pattern that will be useful when mocking with GMock later.
Adding GoogleTest as a component
GoogleTest isn’t part of ESP‑IDF, so we introduce it via a wrapper component located at host_test/gtest/. The wrapper consists only of a CMakeLists.txt file:
# host_test/gtest/CMakeLists.txt
if(IDF_TARGET STREQUAL "linux")
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
# FetchContent must run during the real build phase, not the dependency‑scan phase
if(NOT CMAKE_BUILD_EARLY_EXPANSION)
FetchContent_MakeAvailable(googletest)
endif()
# Register as an INTERFACE library so other components can link to GTest/GMock
add_library(gtest INTERFACE)
target_link_libraries(gtest INTERFACE gtest gmock)
target_include_directories(gtest INTERFACE
${googletest_SOURCE_DIR}/include
)
endif()
Key points
- Linux‑only guard – the component is processed only when the build target is
linux. - FetchContent – downloads GTest at build time; updating the version only requires changing
GIT_TAG. - Build‑phase guard – prevents download attempts during the early dependency‑scan phase.
- INTERFACE library – the wrapper itself compiles nothing; it merely exposes GTest/GMock to projects that
REQUIRESit.
Test project configuration
The test project lives in host_test/test_sum/. Important CMake settings:
# host_test/test_sum/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS
"${CMAKE_CURRENT_LIST_DIR}/../../.." # root of the repo (contains the component under test)
"${CMAKE_CURRENT_LIST_DIR}/../gtest" # GTest wrapper
)
idf_component_register(
SRCS "test_sum.cpp"
INCLUDE_DIRS "."
REQUIRES sum gtest
WHOLE_ARCHIVE # Ensure static constructors (GoogleTest registration) are not discarded
)
- EXTRA_COMPONENT_DIRS points to the component under test (
sum) and the GTest wrapper, which are outside the test project folder. - REQUIRES lists only the needed components, keeping the build fast.
- WHOLE_ARCHIVE forces inclusion of all object files so that GoogleTest can discover the tests.
Example test code
// host_test/test_sum/test_sum.cpp
#include
#include "sum.hpp"
TEST(TestSum, GTestSmokeTest) {
EXPECT_TRUE(true); // verifies that GoogleTest itself runs
}
TEST(TestSum, Add) {
Sum s;
EXPECT_EQ(s.add(2, 3), 5);
EXPECT_EQ(s.add(-1, -4), -5);
EXPECT_EQ(s.add(0, 0), 0);
}
TEST(TestSum, AddConstrained_InRange) {
Sum s;
EXPECT_EQ(s.add_constrained(3, 4), 7); // 7 ≤ 10
}
TEST(TestSum, AddConstrained_AtLimit) {
Sum s;
EXPECT_EQ(s.add_constrained(5, 5), 10); // exactly the limit
}
TEST(TestSum, AddConstrained_OutOfRange) {
Sum s;
EXPECT_EQ(s.add_constrained(6, 5), -1); // 11 > 10 → -1
}
Building and running the host tests
cd 01_basic_test/host_test/test_sum
idf.py --preview set-target linux # Linux target is still experimental
idf.py build
./build/test_sum.elf
Expected output
[==========] Running 6 tests from 1 test suite.
[----------] 6 tests from TestSum
[ RUN ] TestSum.GTestSmokeTest
[ OK ] TestSum.GTestSmokeTest (0 ms)
...
[ PASSED ] 6 tests.
Continuous Integration
The repository includes GitHub Actions workflows that execute the host tests on every push using the official ESP‑IDF Docker container. No hardware is required, so the CI runs on standard GitHub runners.
What’s next?
Future chapters will cover:
- GMock for isolating components via mock objects.
- CMock‑based approach (used internally by ESP‑IDF) for mocking hardware dependencies.
Full source code is available at github.com/aluiziotomazelli/gtest-esp-idf.