Author and run the Ceedling build system for C unit testing - the canonical build orchestration on top of Unity (assertions) + CMock (mocks) + CException (exceptions). Covers ceedling new scaffolding, the project.yml schema (:project / :paths / :files / :defines / :flags / :tools / :test_runner / :cmock / :unity / :cexception / :gcov / :plugins), the task surface (test:all, test:{name}, test:pattern, test:path, release, clean / clobber, gcov:all, module:create, environment, dumpconfig), JUnit XML via the report_tests_* plugins, gcov integration, host vs cross-build flow, and CI wiring. CMock semantics - the generated Expect / Ignore / IgnoreArg / ReturnThruPtr / AddCallback / Stub / ExpectAndThrow family, cmock.yml :plugins, mock naming, tearDown verification, strict-vs-ignore matching - are in references/cmock.md. Use when a C project wants the ThrowTheSwitch trio bundled by one build command, or when authoring / reading CMock mocks. For the Unity assertion API see unity-test-framework-c.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
CMock is, per
github.com/ThrowTheSwitch/CMock,
"a mock and stub generator and runtime for unit testing C" that
"automagically parses your C headers and creates useful and usable
mock interfaces for unit testing". CMock only exists inside the
Ceedling / Unity workflow this skill orchestrates - see
SKILL.md for the build tasks and project.yml wiring,
and unity-test-framework-c for the assertion API.
This reference covers CMock's generated API surface and the cmock.yml plugin model: decoding which plugin produced which API when reading a mock-driven suite, choosing between strict expectations / ignored arguments / return-thru-pointer when authoring a new mock, and migrating a legacy fake-by-hand to a CMock-generated mock.
| Term | Behaviour | CMock realisation |
|---|---|---|
| Stub | Returns canned values; doesn't fail the test | func_IgnoreAndReturn(value) - no expectation, just a default return |
| Mock (strict) | Verifies exact call + args; fails test if unmatched | func_Expect(args) / func_ExpectAndReturn(args, ret) |
| Spy | Records calls for later inspection | func_AddCallback(cb) - callback that records into test-scope state |
| Fake | Lightweight reimplementation | func_Stub(impl) - replace the function with a custom C implementation |
The English vocabulary is from Meszaros's xUnit Test Patterns (book, cite by ISBN 978-0131495050); CMock encodes all four shapes through one generated family.
Given parser.h:
int parse_message(const char *buf, size_t len, message_t *out);CMock generates mock_parser.h and mock_parser.c. The
generated API for parse_message is (per
github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md):
// Strict expectations
void parse_message_Expect(const char *buf, size_t len, message_t *out);
void parse_message_ExpectAndReturn(const char *buf, size_t len, message_t *out, int retval);
// Argument-flexibility (requires :expect_any_args plugin)
void parse_message_ExpectAnyArgs(void);
void parse_message_ExpectAnyArgsAndReturn(int retval);
// Array depth (requires :array plugin)
void parse_message_ExpectWithArray(const char *buf, int buf_depth, size_t len, message_t *out, int out_depth);
void parse_message_ExpectWithArrayAndReturn(const char *buf, int buf_depth, size_t len, message_t *out, int out_depth, int retval);
// Per-argument ignore (requires :ignore_arg plugin)
void parse_message_IgnoreArg_buf(void);
void parse_message_IgnoreArg_len(void);
void parse_message_IgnoreArg_out(void);
// Whole-function ignore (requires :ignore or :ignore_stateless plugin)
void parse_message_Ignore(void);
void parse_message_IgnoreAndReturn(int retval);
void parse_message_StopIgnore(void);
// Pointer-return injection (requires :return_thru_ptr plugin)
void parse_message_ReturnThruPtr_out(message_t *val_to_return);
void parse_message_ReturnArrayThruPtr_out(message_t *val_to_return, int len);
void parse_message_ReturnMemThruPtr_out(message_t *val_to_return, size_t size);
// Callback / stub (requires :callback plugin)
void parse_message_AddCallback(CMOCK_parse_message_CALLBACK callback);
void parse_message_Stub(CMOCK_parse_message_CALLBACK callback);
// Exception throwing (requires :cexception plugin)
void parse_message_ExpectAndThrow(const char *buf, size_t len, message_t *out, CEXCEPTION_T value_to_throw);All of the above family names are cited from the CMock Summary
doc above. The generated function names follow the rigid pattern
<original_function_name>_<CMockVerb>[_<paramName>].
:plugins listEach generated API family is gated on a plugin in cmock.yml
(typically inlined into Ceedling's project.yml under
:cmock: :plugins: - per the same CMock summary doc):
| Plugin | Enables | When to enable |
|---|---|---|
:ignore | _Ignore, _IgnoreAndReturn, _StopIgnore (stateful) | When you want unmatched calls to a function to pass after _Ignore is called |
:ignore_stateless | Same API as :ignore but no per-test state | Faster; use when ignored functions are uninteresting |
:ignore_arg | _IgnoreArg_<param> | When a specific argument isn't part of the assertion |
:expect_any_args | _ExpectAnyArgs, _ExpectAnyArgsAndReturn | When call count matters but args don't |
:array | _ExpectWithArray, _ReturnArrayThruPtr_<param> | When arguments are pointer-to-array of known depth |
:callback | _AddCallback, _Stub | When you need to capture calls or substitute a fake |
:cexception | _ExpectAndThrow | When the module under test propagates CException throws |
:return_thru_ptr | _ReturnThruPtr_<param>, _ReturnMemThruPtr_<param> | When the mocked function writes through an out-pointer |
A minimal cmock.yml for a typical embedded suite:
:cmock:
:mock_prefix: Mock
:mock_suffix: ""
:plugins:
- :ignore
- :ignore_arg
- :expect_any_args
- :array
- :callback
- :return_thru_ptr:cexception is added only if the project uses CException.
Per the CMock summary doc, the mock module name is built from
:mock_prefix + original module name + :mock_suffix. With the
default :mock_prefix: Mock:
| Original | Generated mock module |
|---|---|
parser.h | Mockparser.h and Mockparser.c |
i2c_driver.h | Mocki2c_driver.h and Mocki2c_driver.c |
In a test (test_consumer.c), include the mock header:
#include "unity.h"
#include "Mockparser.h" // CMock-generated
#include "consumer.h" // under testCeedling automatically detects the Mock* include and generates
the mock at test-build time.
CMock hooks into Unity's per-test lifecycle:
| Hook | What CMock does |
|---|---|
setUp() | (Optional) Mockparser_Init() clears prior expectations - Ceedling generates this automatically when test_runner is generated |
tearDown() | Mockparser_Verify() asserts every queued expectation was matched; fails the test via Unity assertion if not |
resetTest() (mid-test) | Per the CMock summary doc, "Call it during a test to have CMock validate everything to this point and start over clean" - useful for staged interaction tests |
Forgetting to register a mock causes link errors, not test failures - the mock is the only definition of the symbol.
CMock's default mode is strict by argument: passed arguments
must memcmp-equal the expected. The user softens with:
| Softener | Effect |
|---|---|
_IgnoreArg_<param> | This call's <param> is not checked |
_ExpectAnyArgs | None of this call's args are checked |
_Ignore / _IgnoreAndReturn | All subsequent calls to this function are ignored until _StopIgnore |
Custom matcher via _AddCallback | Inspect args programmatically and return a comparison |
For pointer arguments to structs, the default is deep-equal by
size - CMock memcompares the pointed-to memory. For string
pointers, treat as strcmp only if the deep-equal of the buffer
matches the string length CMock chose at generation; in practice,
use _IgnoreArg_<param> + a _AddCallback for string-matching.
A consumer module that calls a parser; the test verifies the expected call shape:
// consumer.h - under test
int consume(const char *raw);
// consumer.c
#include "parser.h"
int consume(const char *raw) {
message_t m;
if (parse_message(raw, strlen(raw), &m) != 0) return -1;
return m.kind;
}
// test_consumer.c
#include "unity.h"
#include "Mockparser.h"
#include "consumer.h"
void test_consume_returns_kind_on_success(void) {
message_t out_fixture = { .kind = 7 };
parse_message_ExpectAndReturn("PING", 4, NULL, 0);
parse_message_IgnoreArg_out();
parse_message_ReturnThruPtr_out(&out_fixture);
TEST_ASSERT_EQUAL_INT(7, consume("PING"));
}
void test_consume_returns_negative_one_on_parse_error(void) {
parse_message_ExpectAnyArgsAndReturn(-1);
TEST_ASSERT_EQUAL_INT(-1, consume("BADINPUT"));
}The first test: strict on buf + len, lenient on out, then
write the fixture through the out-pointer. The second: count
matters, args don't.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Mocking everything | Tests become assertion-by-mock; refactors break dozens of mocks | Mock only the boundary (driver, peripheral, OS call); leave pure logic un-mocked |
_ExpectAnyArgs everywhere | "Test passes" but coverage of expected call shape is zero | Use strict _Expect for the interesting args; ignore only the noisy ones |
_Ignore left on globally | Real defects in the SUT-mock interaction are hidden | Pair _Ignore with a clear narrative comment; prefer _IgnoreAndReturn once-per-test |
Mock-side state mutation through _Stub | Tests rely on stub side-effects across calls; hard to reason about | Use _AddCallback to record calls explicitly; assert on the record at the end |
| Forgetting to enable a plugin then using its API | Compile fails on unknown function | Audit :cmock: :plugins: whenever a test uses a new _IgnoreArg_* or _ReturnThruPtr_* |
| Mocking standard-library functions | Pulls libc into mock generation; coverage explodes | Wrap libc behind a project-owned header (e.g. time_provider.h) and mock that |
Asserting Mockxxx_Init not called | Init is generated by Ceedling, not part of the API contract | Don't assert on _Init / _Destroy; assert on the domain calls |
_AddCallback for
structures-with-pointers.