CMock “Fluent” Syntax Proposal
** Fluent ** : effortlessly smooth and flowing (Merriam Webster)
Hi everyone! I’ve been mulling over an alternative CMock syntax for a very long time. The goal would be to be more flexible and expressive, and to be able to optimize some of CMock’s engine internally at the same time. This is my the first public draft of my ideas. I appreciate any thoughts, questions, and opinions. ![]()
Background: Current API
CMock currently generates separate functions/macros for each combination of features. For a function
int read_sensor(SENSOR_T* s, int channel, int* out_val), a test might look like:
// Expect specific args and return a value:
read_sensor_ExpectAndReturn(sensor_ptr, 2, out_ptr, SENSOR_OK);
// Then, separately, ignore one argument:
read_sensor_IgnoreArg_out_val();
// Then, separately, fill data through the pointer:
read_sensor_ReturnThruPtr_out_val(&my_value);
This is three separate macro calls that conceptually describe one interaction. Order matters,
argument lists shift depending on which plugins are active, and it’s easy to forget a step.
The proposal below explores a new direction for expressing mock behavior more coherently.
Core Idea
A single entry macro starts an expectation and accepts clause tokens
describing every aspect of the interaction. Only specify what you care about — omitting
MOCK_CHECK_ARGS means “any arguments,” omitting RETURNS means zero/null/void. The mental model: “Expect this function, with these arguments, returning this value.”
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARG(s, s),
MOCK_CHECK_ARG(channel, 0),
MOCK_FILL_ARG(out_val, &v1),
MOCK_RETURNS(SENSOR_OK)
);
Clauses are nested inside MOCK_EXPECT, so the function name is stated once. Commas inside
any clause’s parentheses are protected by those parentheses and do not split the outer MOCK_EXPECT argument list — MOCK_CHECK_ARG(s, s) and MOCK_FILL_ARG(out_val, &v1) are each seen as a single argument, exactly asMOCK_RETURNS(SENSOR_OK) is.
This design requires C99 for variadic macros (__VA_ARGS__).
Syntax
/* Simplest case: just expect the call (no arg checking, void or zero return) */
MOCK_EXPECT(funcname);
/* Expect with specific arguments checked */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARGS(1, 2, 3)
);
/* Expect with arguments and a return value */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARGS(1, 2, 3),
MOCK_RETURNS(44)
);
/* Don't care how many times this is called */
MOCK_EXPECT(funcname,
MOCK_CALLED_MIN(0)
);
/* Don't care how many times, always return the same value */
MOCK_EXPECT(funcname,
MOCK_CALLED_MIN(0),
MOCK_RETURNS(0)
);
/* Check one named argument — unchecked args are ignored */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARG(b, expected_b),
MOCK_RETURNS(44)
);
/* Array/pointer argument with comparison depth */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARRAY(ptr, 5),
MOCK_RETURNS(SENSOR_OK)
);
/* Fill data through an output pointer — only name the args you care about */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARG(s, s),
MOCK_CHECK_ARG(channel, channel),
MOCK_FILL_ARG(out_val, &my_sensor_value),
MOCK_RETURNS(SENSOR_OK)
);
/* Throw a CException instead of returning */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARGS(&s, 99, out_ptr),
MOCK_THROWS(CHANNEL_OUT_OF_RANGE)
);
/* Call a stub for every call — any arguments, any number of times */
MOCK_EXPECT(funcname,
MOCK_CALLED_MIN(0),
MOCK_CALLBACK(my_sensor_stub)
);
/* Call a stub but also verify arguments */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARGS(&s, channel, out_ptr),
MOCK_CALLBACK(my_sensor_stub)
);
/* Must never be called */
MOCK_NEVER(funcname);
Call Count
Call count is just another clause, nested alongside the others. Three tokens cover every
case: MOCK_CALLED(n) for an exact count, MOCK_CALLED_MIN(n) for a lower bound,MOCK_CALLED_MAX(n)for an upper bound. MOCK_CALLED_MIN and MOCK_CALLED_MAX are orthogonal — combining them expresses a range without a dedicated BETWEEN clause.
/* Default — exactly once (no clause needed) */
MOCK_EXPECT(funcname,
MOCK_RETURNS(44)
);
/* Exactly N times */
MOCK_EXPECT(funcname,
MOCK_CHECK_ARGS(1, 2),
MOCK_RETURNS(44),
MOCK_CALLED(3)
);
/* At least N times */
MOCK_EXPECT(funcname,
MOCK_RETURNS(SENSOR_OK),
MOCK_CALLED_MIN(2)
);
/* At most N times (0 allowed — may not be called at all) */
MOCK_EXPECT(funcname,
MOCK_RETURNS(SENSOR_OK),
MOCK_CALLED_MAX(5)
);
/* Between A and B times — combine MIN and MAX */
MOCK_EXPECT(funcname,
MOCK_RETURNS(SENSOR_OK),
MOCK_CALLED_MIN(2),
MOCK_CALLED_MAX(5)
);
/* Any number of times (including zero) */
MOCK_EXPECT(funcname,
MOCK_CALLED_MIN(0),
MOCK_RETURNS(SENSOR_OK)
);
/* Must never be called — explicit negative assertion */
MOCK_NEVER(funcname);
MOCK_NEVER provides a clear failure message if the function is called at all during the test.
This is functionally the same as not calling MOCK_EXPECT, but this is self-documenting and
could optionally drive diagnostic messages differently.
Interaction with sequenced calls: Count clauses apply to a single pattern. To model
calls with different arguments or return values per invocation, use multiple MOCK_EXPECT
blocks — each implies MOCK_CALLED(1) by default.
/* Three calls, all identical — use MOCK_CALLED */
MOCK_EXPECT(poll_sensor,
MOCK_RETURNS(SENSOR_BUSY),
MOCK_CALLED(3)
);
/* Three calls, different returns each time — use separate MOCK_EXPECTs */
MOCK_EXPECT(poll_sensor, MOCK_RETURNS(SENSOR_IDLE));
MOCK_EXPECT(poll_sensor, MOCK_RETURNS(SENSOR_BUSY));
MOCK_EXPECT(poll_sensor, MOCK_RETURNS(SENSOR_READY));
/* You can use either method at your convenience */
MOCK_EXPECT(poll_sensor, MOCK_RETURNS(SENSOR_IDLE));
MOCK_EXPECT(poll_sensor, MOCK_RETURNS(SENSOR_BUSY), MOCK_CALLED(3) );
MOCK_EXPECT(poll_sensor, MOCK_RETURNS(SENSOR_READY));
Actions
An action is what the mock does when the function is actually called at runtime. The return/termination actions are mutually exclusive — a given expectation has exactly one. MOCK_CALLBACK is additive and may appear alongside any of them.
MOCK_RETURNS(v)— return a value to the callerMOCK_THROWS(ex)— throw a CExceptionMOCK_END_TEST()— exit the test body early and run normal teardownMOCK_CALLBACK(fn)— call a user function; additive, not exclusive (see below)- (void functions with no action clause just return normally)
MOCK_END_TEST longjmps out of the mock — and out of whatever call stack led to it — directly to the test runner. The runner then calls tearDown() and CMock’s verify functions
as normal. Pass or fail is determined by those verifications, exactly as it would be at the natural end of the test. MOCK_END_TEST itself carries no predetermined outcome; it only signals that the test body is done.
/* Fatal error handler that must never return */
void test_out_of_memory_calls_fatal(void)
{
MOCK_EXPECT(fatal_error,
MOCK_CHECK_ARGS(ERR_OUT_OF_MEMORY),
MOCK_END_TEST()
);
trigger_allocation_failure();
/* If we reach here, fatal_error was never called.
MockVerify() in tearDown catches the unfulfilled Expect and fails the test. */
}
/* exit() mock — check the exit code, then end the test */
void test_bad_args_exits_with_code_1(void)
{
MOCK_EXPECT(exit,
MOCK_CHECK_ARGS(1),
MOCK_END_TEST()
);
parse_args(0, NULL);
}
Any expectations set up after the MOCK_END_TEST function in the test body will still be
checked after this mock. If they were never fulfilled before the early exit, they will cause a
failure, as expected. Only set up expectations for calls that will happen before the early exit.
MOCK_END_TEST and call count clauses: MOCK_END_TEST fires on the first invocation and
exits the test — no further calls can occur. This makes certain count clause combinations
logically incoherent, and the generator rejects them with a descriptive error rather than producing a mock that will always fail at verify time.
MOCK_CALLED(N)where N > 1 — the test exits after call 1; N calls can never be satisfied.
Error: “MOCK_END_TEST ends the test on the first call; MOCK_CALLED(3) can never be satisfied —
did you mean MOCK_CALLED(1)?”MOCK_CALLED_MAX(N)— after the first call the test is over; the upper bound is meaningless.
Error: “MOCK_END_TEST ends the test on the first call; MOCK_CALLED_MAX(N) will never be checked.”MOCK_CALLED_MIN(N)where N > 1 — same issue; N calls cannot happen if the test exits on call 1.
Error: “MOCK_END_TEST ends the test on the first call; MOCK_CALLED_MIN(2) can never be satisfied.”MOCK_CALLED_MIN(0)— technically valid (“if it’s called, end the test; if never called, that’s
fine too”), but unusual enough to deserve a warning rather than silent acceptance.
Warning: “MOCK_END_TEST with MOCK_CALLED_MIN(0) means: if this function is called at all, the test
will exit immediately. Verify this is intentional.”MOCK_CALLED(1)or no count clause — correct; no errors emitted.
Automatic MOCK_END_TEST for noreturn functions:
When the generator detects a function declared _Noreturn (C11) or __attribute__((noreturn)), it can automatically apply MOCK_END_TEST as the default action rather than the normal zero-return. This is controlled by a generator config flag (:auto_end_test_on_noreturn: true).
When auto-mode is enabled, three mechanisms ensure the behaviour is discoverable rather
than mysterious:
-
CMock Output — the generator lists each function that received
MOCK_END_TEST
automtically at mock-generation time, visible in the build log. -
Comment in the generated header — each affected declaration is annotated:
/* noreturn — calling this mock exits the test early and runs tearDown */ void fatal_error_CMockOpenExpect(UNITY_LINE_TYPE line);IDEs surface this in hover and autocomplete, so any developer who looks up the signature sees the note immediately.
-
Runtime message — when auto-
MOCK_END_TESTfires, a Unity message is emitted
before the stopping the test:"fatal_error called (noreturn) — ending test early, tearDown will run". This converts a mysterious early exit into a self-explaining event in
the test output.
When auto-mode is disabled (the default), the generator instead emits a warning if a
noreturn mock is set up without an explicit MOCK_END_TEST() clause: “fatal_error is
declared noreturn but no MOCK_END_TEST clause was specified — the mock will return a zero value to its caller, violating the noreturn contract.” This keeps explicit control with
the test author while flagging the likely mistake.
MOCK_CALLBACK(fn) — user-supplied stub function:
MOCK_CALLBACK invokes a user-supplied function every time the mock is called. Unlike the
other action clauses, it is additive: it can appear alongside MOCK_RETURNS, MOCK_THROWS, or MOCK_END_TEST.
The callback function signature mirrors the mocked function — same parameter types and
return type. Depending on the :callback_include_count configuration option, an additional
int call_count parameter is appended (matching the existing CMock callback convention):
/* Mocked function: int read_sensor(SENSOR_T* s, int channel, int* out_val) */
/* Callback without count (default) */
int my_read_sensor_stub(SENSOR_T* s, int channel, int* out_val);
/* Callback with count (:callback_include_count: true) */
int my_read_sensor_stub(SENSOR_T* s, int channel, int* out_val, int call_count);
The callback’s return value is the mock’s return value by default. The other action clauses
override this only in the specific way each one implies:
- No other action — callback return value is returned to the caller.
MOCK_RETURNS(v)— callback fires, thenvis returned instead of the callback’s return value.MOCK_THROWS(ex)— callback fires, then an exception is thrown; the callback’s return value is never used.MOCK_END_TEST()— callback fires, then the test exits; the callback’s return value is never used.
/* Callback alone — stub controls the return value, any number of calls */
MOCK_EXPECT(read_sensor,
MOCK_CALLED_MIN(0),
MOCK_CALLBACK(my_read_sensor_stub)
);
/* Callback fires but return value is fixed regardless of what stub returns */
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARGS(&s, 0, NULL),
MOCK_CALLBACK(my_read_sensor_stub),
MOCK_RETURNS(SENSOR_OK)
);
/* Callback fires (e.g. to log or record state), then exception is thrown */
MOCK_EXPECT(read_sensor,
MOCK_CALLBACK(log_unexpected_call),
MOCK_THROWS(SENSOR_ERROR)
);
/* Callback fires (e.g. to capture arguments), then test exits */
MOCK_EXPECT(fatal_error,
MOCK_CALLBACK(capture_fatal_args),
MOCK_END_TEST()
);
Argument Checks
MOCK_CHECK_ARG(name, value) is the named, selective complement to MOCK_CHECK_ARGS. WhereMOCK_CHECK_ARGS checks all arguments positionally, MOCK_CHECK_ARG checks one argument by name. Arguments not mentioned by any clause are left unchecked — no placeholder values needed.
MOCK_CHECK_ARG(name, v) /* name == v */
MOCK_CHECK_ARG_NE(name, v) /* name != v */
MOCK_CHECK_ARG_LT(name, v) /* name < v */
MOCK_CHECK_ARG_GT(name, v) /* name > v */
MOCK_CHECK_ARG_LE(name, v) /* name <= v */
MOCK_CHECK_ARG_GE(name, v) /* name >= v */
MOCK_CHECK_ARG_NEAR(name, v, tol) /* |name - v| <= tol (any numeric type) */
MOCK_CHECK_ARG_NEAR applies to any numeric argument type — float, double, signed and
unsigned integers. The tolerance is an absolute delta, making it a good substitute for MOCK_CHECK_ARG equality whenever exact bit-for-bit comparison is inappropriate.
The generator knows the argument type and emits the appropriate comparison. Tolerance
correctness — including the sign of the tolerance value — is the caller’s responsibility, exactly as with any other numeric expression in C.
The MOCK_CHECK_ARG* clauses always operate on the dereferenced value. For example,
MOCK_CHECK_ARG_GE(ptr, v) compares *ptr >= v, not the pointer address. This is the case
regardless of the global :smart pointer handling setting. To compare a pointer’s address
explicitly, use MOCK_CHECK_PTR(name) (see Pointer Handling below).
Including multiple MOCK_CHECK_ARG clauses on the same argument checks that argument against each criterion in the order specified — useful for range bounds. Note it IS possible to chain conflicting criteria that will always fail and the engine has no way to detect this. Because the failure will be listed with the line number of the macro, though, it will be easy to track down.
Using MOCK_CHECK_ARGS and MOCK_CHECK_ARG_* are two distinct, non-mixing styles. Use MOCK_CHECK_ARGSwhen you want to check all arguments positionally. Use named MOCK_CHECK_ARG_* clauses when you want selective checking — arguments not mentioned are simply not checked, with no dummy values or explicit exemptions needed. The generator warns if both styles appear in the same expectation, since this is almost always a mistake:
/* WARNING — do not mix styles */
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARGS(&s, 0, NULL), /* positional */
MOCK_CHECK_ARG_GE(channel, 0) /* named — generator warns */
);
/* Use one style or the other */
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARGS(&s, 0, NULL) /* positional: checks all three args */
);
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARG(s, s), /* named: only s and channel are checked */
MOCK_CHECK_ARG_GE(channel, 0), /* out_val is simply not checked */
MOCK_CHECK_ARG_LE(channel, 7),
MOCK_FILL_ARG(out_val, &val),
MOCK_RETURNS(SENSOR_OK)
);
/* Named checks — only specify the args you care about */
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARG(s, s),
MOCK_CHECK_ARG(channel, 2),
MOCK_FILL_ARG(out_val, &val),
MOCK_RETURNS(SENSOR_OK)
);
/* Range check on one arg — combine GE and LT for exclusive upper bound */
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARG(s, s),
MOCK_CHECK_ARG_GE(channel, 0),
MOCK_CHECK_ARG_LT(channel, 8),
MOCK_FILL_ARG(out_val, &val),
MOCK_RETURNS(SENSOR_OK)
);
/* Verify port and timeout are within acceptable bounds */
MOCK_EXPECT(open_connection,
MOCK_CHECK_ARG(host, expected_host),
MOCK_CHECK_ARG_GT(port, 0),
MOCK_CHECK_ARG_LE(port, 65535),
MOCK_CHECK_ARG_GT(timeout_ms, 0),
MOCK_RETURNS(CONN_OK)
);
/* Implicit range — two clauses on the same arg */
MOCK_EXPECT(set_volume,
MOCK_CHECK_ARG_GE(level, 0),
MOCK_CHECK_ARG_LE(level, 100)
);
Ranges are expressed by combining two clauses on the same argument — any mix of GT, GE,LT, LE — giving full control over whether each endpoint is included or excluded.
MOCK_CHECK_ARG_NEAR is the right choice whenever exact equality is inappropriate — for floating-point values and for integer values when some implementation-defined slop is acceptable.
Pointer Handling
Pointer checking behavior differs between the two argument-checking styles:
Named MOCK_CHECK_ARG* clauses operate on the dereferenced value:
/* Checks *cfg_ptr == expected_cfg (struct comparison by value) */
MOCK_EXPECT(configure,
MOCK_CHECK_ARG(cfg_ptr, expected_cfg)
);
/* Checks *level >= 0.0f */
MOCK_EXPECT(set_level,
MOCK_CHECK_ARG_GE(level, 0.0f)
);
/* MOCK_CHECK_PTR is the only named-style opt-in for address comparison */
MOCK_EXPECT(write_data,
MOCK_CHECK_PTR(buf) /* buf == expected_address */
);
MOCK_CHECK_ARGS (positional style) follows CMock’s global :smart pointer handling
setting by default. Optional mode clauses override that default for specific arguments:
/* Default — inherits :smart (typically deref comparison) */
MOCK_EXPECT(configure,
MOCK_CHECK_ARGS(cfg_ptr, flags)
);
/* Deref — explicit single pointed-to value: *actual == *expected */
MOCK_EXPECT(configure,
MOCK_CHECK_ARGS(cfg_ptr, flags),
MOCK_CHECK_DEREF(cfg_ptr)
);
/* Array — compare N pointed-to elements: actual[0..N-1] == expected[0..N-1] */
MOCK_EXPECT(send_packet,
MOCK_CHECK_ARGS(packet_ptr, len),
MOCK_CHECK_ARRAY(packet_ptr, 4)
);
/* Address — compare the pointer value itself */
MOCK_EXPECT(write_data,
MOCK_CHECK_ARGS(buf, len),
MOCK_CHECK_PTR(buf)
);
To leave a pointer argument unchecked, use named MOCK_CHECK_ARG* clauses for the arguments you care about and say nothing about the rest.
Fill modes:
/* Scalar fill — write sizeof(*T) through the pointer */
MOCK_EXPECT(get_config,
MOCK_FILL_ARG(cfg_ptr, &expected_config)
);
/* Array fill — write N × sizeof(*T) through the pointer */
MOCK_EXPECT(read_samples,
MOCK_CHECK_ARG(count, 8),
MOCK_FILL_ARRAY(buf, sample_array, 8)
);
/* Memory fill — write N raw bytes through the pointer */
MOCK_EXPECT(read_raw,
MOCK_CHECK_ARG(max_len, 32),
MOCK_FILL_MEM(void_ptr, byte_buffer, 32)
);
The three fill sizes map directly to the existing ReturnThruPtr / ReturnArrayThruPtr /
ReturnMemThruPtr trio. MOCK_FILL_MEM exists specifically for void* parameters and trailing variable-length structs, where the element type is unknown or non-uniform and an explicit byte count is the only safe option.
Checking and filling are independent and compose freely:
/* Typical output pointer: check the inputs, fill the output */
MOCK_EXPECT(read_sensor,
MOCK_CHECK_ARG(s, s),
MOCK_CHECK_ARG(channel, channel),
MOCK_FILL_ARG(out_val, &my_value),
MOCK_RETURNS(SENSOR_OK)
);
/* Typical input pointer: verify the data sent in */
MOCK_EXPECT(write_config,
MOCK_CHECK_ARGS(cfg_ptr),
MOCK_CHECK_DEREF(cfg_ptr)
);
/* Buffer checked by address AND filled */
MOCK_EXPECT(dma_transfer,
MOCK_CHECK_ARGS(known_buf, len),
MOCK_FILL_MEM(known_buf, response_bytes, 64),
MOCK_RETURNS(DMA_OK)
);
Ordering
The default ordering mode is determined by CMock’s existing :strict_ordering configuration option — the same generation-time setting that controls this today. What the new syntax adds is the ability to override that default at runtime, within a test, without changing the config. Two test-level macros change the current mode; an optional clause on any individual expectation overrides the mode for just that one call.
Test-level control:
MOCK_ENFORCE_ORDER(); /* all subsequent expectations must fire in setup order */
MOCK_ALLOW_ANY_ORDER(); /* expectations may fire in any order */
These take effect immediately and apply to every expectation set up after the call. Switching back and forth within a single test is allowed. Only expectations which are set up in an “area” where enforcement is enabled will contribute to “order” and be validated. All other calls are “ignored” by the order checking.
Per-expectation clause:
MOCK_ORDERED() /* this expectation must fire at its queue position */
MOCK_UNORDERED() /* this expectation may fire at any point */
The clause overrides the current test-level mode for that one expectation only. An MOCK_ORDERED() expectation set up while MOCK_ALLOW_ANY_ORDER() is active still enforces its position; an MOCK_UNORDERED() expectation set up while MOCK_ENFORCE_ORDER() is active is exempt from the sequence.
Ordered expectations enforce their relative position with respect to other ordered expectations. Unordered expectations form a free pool — they can be satisfied by any matching call at any point, regardless of what ordered expectations have or haven’t fired yet. The relative position between an ordered and an unordered expectation is not enforced in either direction.
It is important to note that if the SAME mock is expected multiple times, it will continue to
queue the expectations in the order they are provided, no matter if ORDERED or UNORDERED is currently active. As with UNORDERED mode itself, the UNORDERED trait only applies to the
order between mock functions.
Similarly, when an MOCK_ORDERED() expectation carries MOCK_CALLED(N), all N calls must
fire before the next ordered expectation becomes eligible. They are treated as N consecutive
ordered slots. Unordered expectations may still be satisfied between any of those N calls, exactly as they can be at any other point in the sequence. No other ordered expectation can
fire until the full count is consumed.
void test_initialization_sequence_is_ordered(void)
{
SENSOR_T s;
MOCK_ENFORCE_ORDER();
/* These three must fire in this exact sequence */
MOCK_EXPECT(power_on, MOCK_CHECK_ARGS(&s));
MOCK_EXPECT(calibrate_sensor, MOCK_CHECK_ARGS(&s));
MOCK_EXPECT(enable_sensor, MOCK_CHECK_ARGS(&s));
/* log_event may be called at any point — exempt from the sequence */
MOCK_EXPECT(log_event, MOCK_CALLED_MIN(1), MOCK_UNORDERED());
run_initialization(&s);
MockSensor_Verify();
}
void test_teardown_order_relaxed(void)
{
SENSOR_T s;
/* Default or inherited mode: ordered */
MOCK_EXPECT(read_sensor, MOCK_CHECK_ARGS(&s, 0, NULL), MOCK_RETURNS(SENSOR_OK));
MOCK_EXPECT(read_sensor, MOCK_CHECK_ARGS(&s, 1, NULL), MOCK_RETURNS(SENSOR_OK));
/* Switch to unordered for cleanup calls — either may come first */
MOCK_ALLOW_ANY_ORDER();
MOCK_EXPECT(flush_sensor, MOCK_CHECK_ARGS(&s));
MOCK_EXPECT(power_off, MOCK_CHECK_ARGS(&s));
run_two_reads_then_cleanup(&s);
MockSensor_Verify();
}
Implementation Notes
MOCK_EXPECT(fn, ...) is a variadic macro. It opens an expectation instance for fn
and dispatches each clause token to the appropriate generated function via ## token
pasting — the function name is in scope for the entire expansion, so clause tokens never
name it again.
The clause names (MOCK_CHECK_ARGS, MOCK_CHECK_ARG, MOCK_FILL_ARG, MOCK_RETURNS, etc.) are not
standalone macros. They are dispatched by MOCK_EXPECT using token concatenation:
MOCK_CHECK_ARG(channel, 2) inside a MOCK_EXPECT(read_sensor, ...) call produces
read_sensor_CMockCheckArg_channel(2). The generator emits the typed per-function
implementations; MOCK_EXPECT wires clause tokens to those implementations at expansion
time.
/* Generated into mock_sensor.h — typed implementations for clause dispatch */
void read_sensor_CMockOpenExpect(UNITY_LINE_TYPE line);
void read_sensor_CMockSetArgs(SENSOR_T* s, int channel, int* out_val);
void read_sensor_CMockCheckArg_s(SENSOR_T* expected);
void read_sensor_CMockCheckArg_channel(int expected);
void read_sensor_CMockSetReturn(int retval);
void read_sensor_CMockSetAnyArgs(void);
void read_sensor_CMockSetFill_out_val(int* src);
void read_sensor_CMockCheckArray_s(int depth);
void read_sensor_CMockSetThrow(CEXCEPTION_T ex);
/* ... etc for all active plugins and argument names ... */
Generator Configuration
The :syntax option controls which API is emitted:
:syntax: :fluent # clause-based API only (default for new projects)
:syntax: :legacy # original generated functions only
:syntax: :both # emit both sets — supports incremental migration
When :both is active, the generated header contains both the legacy functions
(read_sensor_ExpectAndReturn, read_sensor_IgnoreArg_out_val, etc.) and the fluent
clause dispatch infrastructure. This lets a project adopt the new notation file-by-file
without a flag-day rewrite.
Strengths
- Function name stated once per interaction — no repetition across clauses
- Specify exactly what you need; omit everything else
- Adding a clause to an existing test does not require rewriting any other line
- New plugins add new clause tokens; no existing call sites change
- Each interaction is syntactically grouped — no implicit pairing between separate statements
- Clause names in source code serve as readable labels during review
Weaknesses
- Requires C99; not suitable for strict C89 targets
MOCK_EXPECTbecomes complex to generate correctly, since it must dispatch each clause type and forward arguments to the right generated function