Home C / C++ Testing in C: CTest, CMocka, and Unity – A Practical Guide
Intermediate 3 min · July 13, 2026

Testing in C: CTest, CMocka, and Unity – A Practical Guide

Learn how to unit test C code using CTest, CMocka, and Unity.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical C and C++ systems. Drawn from code that ran under real load.

Follow
Production
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 20-25 min read
  • Basic knowledge of C programming (functions, pointers, memory management)
  • Familiarity with CMake build system
  • A C compiler (GCC or Clang) installed
  • Optional: CMocka and Unity libraries installed
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • CTest is CMake's test runner, not a unit test framework; it orchestrates test executables.
  • CMocka provides assertion macros and mock support for C unit testing.
  • Unity is a lightweight, single-header unit test framework ideal for embedded systems.
  • All three can be combined: write tests with Unity/CMocka, run them via CTest.
  • Proper testing catches memory leaks, buffer overflows, and logic errors early.
✦ Definition~90s read
What is Testing in C?

Testing in C using CTest, CMocka, and Unity is the practice of writing automated unit tests to verify the correctness of C code, catch bugs early, and ensure reliability in production systems.

Think of testing like a safety net for a trapeze artist.
Plain-English First

Think of testing like a safety net for a trapeze artist. CTest is the ringmaster who calls out each trick, CMocka and Unity are the nets that catch mistakes. Without them, you risk crashing your program in front of a live audience.

Imagine deploying a payment processing system that silently corrupts transaction amounts due to an integer overflow. Or a medical device that miscalculates drug dosages because of an off-by-one error. These are not hypotheticals—they are real-world consequences of untested C code. C remains the backbone of embedded systems, operating systems, and high-performance applications, where reliability is non-negotiable. Yet many C developers rely on ad-hoc testing or skip it entirely, assuming their code is correct. This tutorial introduces three essential testing frameworks: CTest, CMocka, and Unity. CTest is CMake's test driver, perfect for automating test execution. CMocka offers rich assertions and mock functions for complex dependencies. Unity is a minimalist framework ideal for resource-constrained environments. By the end, you'll be able to write robust unit tests, integrate them into your build system, and avoid common pitfalls that lead to production outages.

1. Setting Up CTest with CMake

CTest is part of CMake and provides a way to run tests and report results. It does not write tests for you; you need a test framework like CMocka or Unity. First, create a CMakeLists.txt that adds test executables and enables testing. Use enable_testing() and add_test() to register each test. CTest can run tests in parallel, filter by name, and output results in various formats (JUnit, XML). For example, to add a test named 'test_math' that runs the executable 'test_math', you write:

``cmake enable_testing() add_executable(test_math test_math.c) add_test(NAME test_math COMMAND test_math) ``

Then run cmake --build . && ctest to execute all tests. CTest returns non-zero if any test fails, making it CI-friendly.

CMakeLists.txtC
1
2
3
4
5
6
7
8
9
10
11
12
13
cmake_minimum_required(VERSION 3.10)
project(MyProject C)

enable_testing()

add_executable(test_math test_math.c)
target_link_libraries(test_math PRIVATE cmocka)
add_test(NAME test_math COMMAND test_math)

add_executable(test_string test_string.c)
target_link_libraries(test_string PRIVATE unity)
add_test(NAME test_string COMMAND test_string)
💡CTest Filters
📊 Production Insight
In CI, always run ctest --output-on-failure to see test output immediately.
🎯 Key Takeaway
CTest is a test runner; you still need a unit test framework to write assertions.

2. Writing Unit Tests with CMocka

CMocka is a lightweight C unit testing library that supports mock objects, assertion macros, and test fixtures. To use it, include <cmocka.h> and link against -lcmocka. Each test is a function returning void that uses assert_int_equal, assert_string_equal, etc. Group tests using cmocka_unit_test_setup_teardown for setup/teardown. Example: test a function add that adds two integers.

```c #include <stdarg.h> #include <stddef.h> #include <setjmp.h> #include <cmocka.h>

int add(int a, int b) { return a + b; }

static void test_add_positive(void **state) { assert_int_equal(add(2, 3), 5); }

static void test_add_negative(void **state) { assert_int_equal(add(-1, -2), -3); }

int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_add_positive), cmocka_unit_test(test_add_negative), }; return cmocka_run_group_tests(tests, NULL, NULL); } ```

Compile with gcc test_add.c -lcmocka -o test_add && ./test_add.

test_add.cC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>

int add(int a, int b) {
    return a + b;
}

static void test_add_positive(void **state) {
    assert_int_equal(add(2, 3), 5);
}

static void test_add_negative(void **state) {
    assert_int_equal(add(-1, -2), -3);
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(test_add_positive),
        cmocka_unit_test(test_add_negative),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}
Output
[==========] Running 2 test(s).
[ RUN ] test_add_positive
[ OK ] test_add_positive
[ RUN ] test_add_negative
[ OK ] test_add_negative
[==========] 2 test(s) run. 0 failed.
🔥Mocking with CMocka
📊 Production Insight
Always use cmocka_set_test_filter() to run specific tests during debugging.
🎯 Key Takeaway
CMocka offers rich assertions and mock support, ideal for testing complex logic with dependencies.

3. Lightweight Testing with Unity

Unity is a single-header C unit test framework designed for embedded systems. It has no dependencies and provides simple macros like TEST_ASSERT_EQUAL, TEST_ASSERT_TRUE, etc. To use Unity, include unity.h and define a setUp and tearDown function if needed. Tests are functions that call RUN_TEST. Example:

```c #include "unity.h"

void setUp(void) {} void tearDown(void) {}

int multiply(int a, int b) { return a * b; }

void test_multiply_positive(void) { TEST_ASSERT_EQUAL_INT(6, multiply(2, 3)); }

void test_multiply_zero(void) { TEST_ASSERT_EQUAL_INT(0, multiply(5, 0)); }

int main(void) { UNITY_BEGIN(); RUN_TEST(test_multiply_positive); RUN_TEST(test_multiply_zero); return UNITY_END(); } ```

Compile with gcc test_multiply.c unity.c -o test_multiply && ./test_multiply. Unity outputs a simple pass/fail report.

test_multiply.cC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "unity.h"

void setUp(void) {}
void tearDown(void) {}

int multiply(int a, int b) {
    return a * b;
}

void test_multiply_positive(void) {
    TEST_ASSERT_EQUAL_INT(6, multiply(2, 3));
}

void test_multiply_zero(void) {
    TEST_ASSERT_EQUAL_INT(0, multiply(5, 0));
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_multiply_positive);
    RUN_TEST(test_multiply_zero);
    return UNITY_END();
}
Output
test_multiply_positive PASS
test_multiply_zero PASS
-----------------------
2 Tests 0 Failures 0 Ignored
⚠ Unity's Minimalism
📊 Production Insight
Unity's output is easy to parse, making it suitable for automated test reporting in CI pipelines.
🎯 Key Takeaway
Unity is perfect for embedded systems or projects where minimal overhead is critical.

4. Integrating Tests with CTest and CMake

To combine CTest with CMocka or Unity, you need to register each test executable in CMakeLists.txt. Use add_test with the executable name. You can also pass arguments to tests, e.g., add_test(NAME test_math COMMAND test_math --verbose). CTest supports test fixtures: FIXTURES_SETUP, FIXTURES_CLEANUP. Example for a project with multiple test files:

```cmake cmake_minimum_required(VERSION 3.10) project(Calculator C)

enable_testing()

# CMocka tests add_executable(test_add test_add.c) target_link_libraries(test_add PRIVATE cmocka) add_test(NAME test_add COMMAND test_add)

# Unity tests add_executable(test_multiply test_multiply.c unity.c) add_test(NAME test_multiply COMMAND test_multiply) ```

Then build and run: mkdir build && cd build && cmake .. && make && ctest. CTest will run both tests and report results. Use ctest -V for verbose output.

CMakeLists.txtC
1
2
3
4
5
6
7
8
9
10
11
12
cmake_minimum_required(VERSION 3.10)
project(Calculator C)

enable_testing()

add_executable(test_add test_add.c)
target_link_libraries(test_add PRIVATE cmocka)
add_test(NAME test_add COMMAND test_add)

add_executable(test_multiply test_multiply.c unity.c)
add_test(NAME test_multiply COMMAND test_multiply)
💡Parallel Test Execution
📊 Production Insight
Set CTEST_OUTPUT_ON_FAILURE=1 environment variable to see test output on failure in CI.
🎯 Key Takeaway
CTest orchestrates test execution; each test executable is a separate target.

5. Mocking External Dependencies with CMocka

Mocking is essential when testing code that calls external functions (e.g., database, network). CMocka provides will_return() to set return values and mock() to retrieve them. Use expect_* functions to verify arguments. Example: test a function process_data that calls get_sensor_value().

```c #include <cmocka.h>

int get_sensor_value(void); // external

int process_data(void) { int val = get_sensor_value(); if (val < 0) return -1; return val * 2; }

// Mock implementation int __wrap_get_sensor_value(void) { return (int)mock(); }

static void test_process_data_positive(void **state) { will_return(__wrap_get_sensor_value, 10); assert_int_equal(process_data(), 20); }

static void test_process_data_negative(void **state) { will_return(__wrap_get_sensor_value, -1); assert_int_equal(process_data(), -1); }

int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_process_data_positive), cmocka_unit_test(test_process_data_negative), }; return cmocka_run_group_tests(tests, NULL, NULL); } ```

Link with -Wl,--wrap=get_sensor_value to replace the real function with the mock.

test_process.cC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cmocka.h>

int get_sensor_value(void);

int process_data(void) {
    int val = get_sensor_value();
    if (val < 0) return -1;
    return val * 2;
}

int __wrap_get_sensor_value(void) {
    return (int)mock();
}

static void test_process_data_positive(void **state) {
    will_return(__wrap_get_sensor_value, 10);
    assert_int_equal(process_data(), 20);
}

static void test_process_data_negative(void **state) {
    will_return(__wrap_get_sensor_value, -1);
    assert_int_equal(process_data(), -1);
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(test_process_data_positive),
        cmocka_unit_test(test_process_data_negative),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}
Output
[==========] Running 2 test(s).
[ RUN ] test_process_data_positive
[ OK ] test_process_data_positive
[ RUN ] test_process_data_negative
[ OK ] test_process_data_negative
[==========] 2 test(s) run. 0 failed.
🔥Linker Wrapping
📊 Production Insight
Always mock time-sensitive functions (e.g., time()) to make tests deterministic.
🎯 Key Takeaway
Mocking isolates the unit under test, ensuring failures are due to the code, not external systems.

6. Testing Memory Management and Edge Cases

C programs are prone to memory errors. Unit tests should verify correct memory allocation, deallocation, and boundary conditions. Use CMocka's assert_ptr_equal to check pointers, and expect_any for flexible argument matching. For example, test a function that allocates a buffer and copies a string:

```c #include <cmocka.h> #include <stdlib.h> #include <string.h>

char duplicate_string(const char src) { if (src == NULL) return NULL; size_t len = strlen(src) + 1; char* dst = malloc(len); if (dst) memcpy(dst, src, len); return dst; }

static void test_duplicate_normal(void *state) { char result = duplicate_string("hello"); assert_non_null(result); assert_string_equal(result, "hello"); free(result); }

static void test_duplicate_empty(void *state) { char result = duplicate_string(""); assert_non_null(result); assert_string_equal(result, ""); free(result); }

static void test_duplicate_null(void **state) { assert_null(duplicate_string(NULL)); }

int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_duplicate_normal), cmocka_unit_test(test_duplicate_empty), cmocka_unit_test(test_duplicate_null), }; return cmocka_run_group_tests(tests, NULL, NULL); } ```

This tests null input, empty string, and normal case. Always free allocated memory in tests to avoid leaks.

test_string_dup.cC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cmocka.h>
#include <stdlib.h>
#include <string.h>

char* duplicate_string(const char* src) {
    if (src == NULL) return NULL;
    size_t len = strlen(src) + 1;
    char* dst = malloc(len);
    if (dst) memcpy(dst, src, len);
    return dst;
}

static void test_duplicate_normal(void **state) {
    char* result = duplicate_string("hello");
    assert_non_null(result);
    assert_string_equal(result, "hello");
    free(result);
}

static void test_duplicate_empty(void **state) {
    char* result = duplicate_string("");
    assert_non_null(result);
    assert_string_equal(result, "");
    free(result);
}

static void test_duplicate_null(void **state) {
    assert_null(duplicate_string(NULL));
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(test_duplicate_normal),
        cmocka_unit_test(test_duplicate_empty),
        cmocka_unit_test(test_duplicate_null),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}
Output
[==========] Running 3 test(s).
[ RUN ] test_duplicate_normal
[ OK ] test_duplicate_normal
[ RUN ] test_duplicate_empty
[ OK ] test_duplicate_empty
[ RUN ] test_duplicate_null
[ OK ] test_duplicate_null
[==========] 3 test(s) run. 0 failed.
⚠ Memory Leaks in Tests
📊 Production Insight
In embedded systems, also test for stack overflow and buffer overrun using static analysis tools.
🎯 Key Takeaway
Test edge cases like NULL inputs, empty strings, and boundary values to prevent crashes.

7. Best Practices and CI Integration

To get the most out of testing, follow these best practices
  • Write tests before code (TDD) or alongside it.
  • Keep tests small and focused on one behavior.
  • Use descriptive test names.
  • Run tests frequently, ideally on every commit via CI.
  • Measure code coverage (e.g., with gcov) to identify untested paths.
  • Use CMocka's cmocka_print_error() for custom error messages.

For CI integration, add a step to build and run tests. Example GitHub Actions workflow:

``yaml name: C Test on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install dependencies run: sudo apt-get install cmake libcmocka-dev - name: Build and test run: | mkdir build && cd build cmake .. make ctest --output-on-failure ``

This ensures tests run automatically on every push.

.github/workflows/test.ymlC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: C Test
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Install dependencies
      run: sudo apt-get install cmake libcmocka-dev
    - name: Build and test
      run: |
        mkdir build && cd build
        cmake ..
        make
        ctest --output-on-failure
💡Coverage Reports
📊 Production Insight
Set up nightly builds with extended tests (e.g., stress tests, memory checks) to catch intermittent issues.
🎯 Key Takeaway
Automated testing in CI catches regressions early and ensures code quality.
● Production incidentPOST-MORTEMseverity: high

The Silent Data Corruption: A $1M Bug

Symptom
Customers reported random packet drops and retransmissions, but logs showed no errors.
Assumption
Developers assumed hardware issues or network congestion.
Root cause
A buffer overflow in a string parsing function, triggered only under specific traffic patterns. No unit tests covered that code path.
Fix
Added unit tests with Unity to cover edge cases, fixed the buffer size, and integrated tests into CI with CTest.
Key lesson
  • Always test boundary conditions (e.g., maximum input length).
  • Use memory sanitizers (e.g., AddressSanitizer) in test builds.
  • Integrate tests into CI to catch regressions early.
  • Mock external dependencies to isolate unit tests.
  • Review code coverage reports to identify untested paths.
Production debug guideSymptom to Action4 entries
Symptom · 01
Test crashes with segmentation fault
Fix
Run test under Valgrind or AddressSanitizer to detect memory errors.
Symptom · 02
Assertion fails only on certain inputs
Fix
Add debug prints or use a debugger (GDB) with a breakpoint at the assertion.
Symptom · 03
Mock function not called as expected
Fix
Check mock expectations and verify call order with CMocka's expect_* functions.
Symptom · 04
Test passes locally but fails in CI
Fix
Check environment differences (compiler version, library paths, build flags).
★ Quick Debug Cheat SheetCommon test failures and immediate actions
Segfault in test
Immediate action
Compile with -fsanitize=address
Commands
gcc -fsanitize=address -g test.c -o test
./test
Fix now
Fix buffer overflow or null pointer dereference.
Assertion fails+
Immediate action
Print actual vs expected values
Commands
printf("Expected %d, got %d\n", expected, actual);
gdb ./test
Fix now
Correct the logic or update test data.
Mock not called+
Immediate action
Check mock setup order
Commands
will_return(mock_func, value);
expect_value(mock_func, param, val);
Fix now
Ensure mock expectations match actual calls.
FeatureCTestCMockaUnity
TypeTest runnerUnit test frameworkUnit test framework
AssertionsNoneRich set (int, string, pointer, etc.)Basic (equal, true, false)
MockingNoYes (via linker wrapping)No (requires external tool)
DependenciesCMakelibcmockaNone (single header)
Best forAutomating test executionComplex projects with dependenciesEmbedded systems, minimal footprint
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
CMakeLists.txtcmake_minimum_required(VERSION 3.10)1. Setting Up CTest with CMake
test_add.cint add(int a, int b) {2. Writing Unit Tests with CMocka
test_multiply.cvoid setUp(void) {}3. Lightweight Testing with Unity
test_process.cint get_sensor_value(void);5. Mocking External Dependencies with CMocka
test_string_dup.cchar* duplicate_string(const char* src) {6. Testing Memory Management and Edge Cases
.githubworkflowstest.ymlname: C Test7. Best Practices and CI Integration

Key takeaways

1
CTest is a test runner; CMocka and Unity are unit test frameworks with different strengths.
2
Mocking isolates units from external dependencies, making tests deterministic.
3
Always test edge cases and memory management to prevent crashes and leaks.
4
Integrate tests into CI with CTest to catch regressions automatically.
5
Use coverage tools to identify untested code paths and improve test quality.

Common mistakes to avoid

5 patterns
×

Not testing edge cases like NULL or empty inputs.

×

Forgetting to free memory in tests.

×

Using global state without resetting between tests.

×

Mocking too many functions, making tests brittle.

×

Not integrating tests into CI.

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
Explain how CTest integrates with CMake to run unit tests.
Q02SENIOR
Write a CMocka test that mocks a function returning an integer.
Q03SENIOR
How would you test a function that allocates memory and ensure no leaks?
Q04SENIOR
Compare CMocka and Unity for unit testing in C.
Q05SENIOR
Describe a real-world scenario where unit testing prevented a production...
Q01 of 05JUNIOR

Explain how CTest integrates with CMake to run unit tests.

ANSWER
CTest is enabled via enable_testing() in CMakeLists.txt. Test executables are added with add_executable, and each is registered with add_test(NAME <name> COMMAND <executable>). Running ctest executes all registered tests and reports results.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between CTest and CMocka?
02
Can I use Unity with CTest?
03
How do I mock functions with Unity?
04
What is the best framework for embedded systems?
05
How do I test code that uses global variables?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical C and C++ systems. Drawn from code that ran under real load.

Follow
Verified
production tested
July 13, 2026
last updated
2,073
articles · all by Naren
🔥

That's C Basics. Mark it forged?

3 min read · try the examples if you haven't

Previous
Build Tools for C: Make, CMake, and Meson
22 / 24 · C Basics
Next
Embedded C Programming: Patterns and Pitfalls