Much of the power of CMock comes from its ability to automatically validate that the arguments passed to mocked functions are the values that were expected to be passed. CMock puts a lot of effort into guessing how the user would most like to see those values compared, and then represented when failures are encountered.
Like Unity, CMock follows a philosophy of making its best guesses, and then allowing the user to explicitly specify any features that they would like to change or customize.
| Situation | Recommended Option |
|---|---|
Built-in C types (int, uint8_t, float, …) |
Nothing — Option 1 handles these automatically |
Simple typedef or #define alias of a known type |
Option 2: add a :treat_as entry |
Small enum type |
Option 2: map to INT8, INT16, or INT |
| Opaque handle / function pointer where only identity matters | Option 2: map to PTR |
typedef'd fixed-size array |
Option 2: :treat_as_array |
Legacy typedef void MY_VOID |
Option 2: :treat_as_void |
struct or union needing field-level comparison |
Option 3: custom assertion + :unity_helper_path |
Pointer to a struct with a custom assertion |
Option 2 + 3: custom assertion, then :treat_as the pointer |
| Type that changes meaning per test, or one-off complex logic | Option 4: Callback |
| Last resort — type is unknown and rough equality is enough | Option 1b: memcmp fallback (automatic) |
First, if you're dealing with C's standard types, there is nothing
further you need to do. CMock will choose an appropriate assertion
from Unity's list of assertions and will perform the comparison and
display using that. For example, if you specify a short, then it's
very likely CMock will compare using TEST_ASSERT_EQUAL_INT16. For
unsigned values, it assumes you'd like them displayed in hex. Are you
interested in comparing a const char*? That would be Unity's
string comparison.
What if you have some other type of pointer? If you've instructed
CMock to compare pointers, it'll use TEST_ASSERT_EQUAL_PTR.
Otherwise it'll use dereference the value being pointed at and
compare that for you. (Read more about the Array plugin for more
details on how this all works). The TYPE being pointed to follows the
same rules as the those above... so if they're common types, for example
unsigned char*, then CMock will choose to compare using the
logical assertion (in this case TEST_ASSERT_EQUAL_HEX8).
A quick note about floating point types: we're calling the assertions
TEST_ASSERT_EQUAL_FLOAT (for example), but don't worry... these
assertions are actually checking to make sure that the values are
"incredibly close" to the desired value instead of identical. This
is because many numbers can be represented in multiple ways when
using floating point. These differences are out of the control of
the user, for the most part. You can ready more about this in the
Unity documentation if you're interested in the details.
So what happens when CMock doesn't recognize the type being used? This will happen for any custom types being used. What constitutes a custom type?
- You've used
#defineto create an alias for a standard type - You've used
typedefto create an alias for a standard type - You've created an
enumtype - You've created a
uniontype - You've created a
structtype - You're working with a function pointer
When CMock doesn't recognize the type as a standard type, (and
assuming you don't have a better option specified, as any of the
options below), it will fall back to performing a memory
comparison using TEST_ASSERT_EQUAL_MEMORY. For the most part,
this is effective, but the reported failures are not terribly
descriptive.
WARNING: There is one important instance where this fallback method
doesn't work at all. If the custom type is a struct and that
struct isn't packed, then it's possible you can get false failures
when the unused bytes between fields differ. For an unpacked struct,
it's important that you either use option 3 or 4 below, or ignore that
particular argument (and possibly test it manually yourself).
CMock maintains a list of non-standard types which are basically
aliases of standard types. For example, a common shorthand for
a single-byte unsigned integer might be u8 or U8 or UINT8.
Any of these can simply be mapped to the standard
TEST_ASSERT_EQUAL_HEX8.
CMock ships with a built-in :treat_as list that already covers the
most common type aliases found in embedded C codebases. You get all
of these for free without any configuration:
| C Type | Unity Assertion Used |
|---|---|
int |
INT |
char |
INT8 |
short |
INT16 |
long |
INT |
unsigned int |
HEX32 |
unsigned long |
HEX32 |
unsigned short |
HEX16 |
unsigned char |
HEX8 |
int8_t / INT8_T / int8 |
INT8 |
int16_t / INT16_T / int16 |
INT16 |
int32_t / INT32_T / int32 |
INT |
uint8_t / UINT8_T / uint8 / UINT8 |
HEX8 |
uint16_t / UINT16_T / uint16 / UINT16 |
HEX16 |
uint32_t / UINT32_T / uint32 / UINT32 |
HEX32 |
bool / bool_t / BOOL / BOOL_T |
INT |
char* |
STRING |
pCHAR / cstring / CSTRING |
STRING |
void* |
HEX8_ARRAY |
float / double |
FLOAT |
The right-hand side of each mapping is the suffix of the Unity assertion
that will be used. HEX8 means CMock will call TEST_ASSERT_EQUAL_HEX8,
for instance. Pointer variants (ending in *) map to the corresponding
array assertion (e.g. HEX8* → TEST_ASSERT_EQUAL_HEX8_ARRAY).
While CMock has its own list of :treat_as mappings, you can
add your own pairings to this list. This works especially well for
the following types:
- aliases of standard types using
#defineortypedef enumtypes (works well asINT8or whatever size your enums are)- function pointers often work well as
PTRcomparisons uniontypes sometimes make sense to treat as the largest type... but this is a judgement call
Your entries merge with the defaults — you are only adding or
overriding specific types, not replacing the entire list. To remove
a default mapping, set its value to nil.
Here is a YAML configuration example:
:cmock:
:treat_as:
MY_BOOL: INT # typedef bool MY_BOOL → compare as int
MY_U8: HEX8 # typedef uint8_t MY_U8 → compare as hex byte
MY_U16: HEX16
MY_U32: HEX32
STATUS_T: INT8 # small enum → compare as signed byte
HANDLE_T: PTR # opaque pointer → compare pointer addresses
float: nil # remove the default float mapping (unusual)Or from Ruby:
CMock.new(
treat_as: {
'MY_BOOL' => 'INT',
'STATUS_T' => 'INT8',
'HANDLE_T' => 'PTR',
}
).setup_mocks('my_module.h')You can map pointer-to-custom-type the same way. Use a * suffix on
the right-hand side to indicate the comparison should use the array
variant of the assertion:
:treat_as:
MY_DATA_PTR: HEX8* # compares the bytes pointed to, not the addressTwo narrower variants of :treat_as handle specific edge cases:
:treat_as_array — for types that are themselves typedef'd arrays,
such as typedef int TenIntegers[10];. This is a hash of typedef name
to element type:
:cmock:
:treat_as_array:
TenIntegers: int
MyBuffer: uint8_tThis lets CMock treat parameters of these types the same way it would
treat a pointer-plus-count, enabling features like ExpectWithArray
and ReturnArrayThruPtr.
:treat_as_void — for legacy codebases that typedef void to a
custom name (e.g. typedef void MY_VOID;). Add such names here so
CMock knows functions returning or accepting that type are effectively
void:
:cmock:
:treat_as_void:
- MY_VOID
- NORETURN_TCMock has the ability to use custom assertions, if you form them according to certain specifications. Creating a custom assertion can be a bit of work, But the reward is that, once you've done so, you can use those assertions within your own tests AND CMock will magically use them within its own mocks.
To accomplish this, we're going tackle multiple steps:
- Write a custom assertion function
- Wrap it in a
UNITY_TEST_macro - Wrap it in a
TEST_macro - Inform CMock that it exists
Let's look at each of those steps in detail:
A custom assertion is a function which accepts a standard set of inputs, and then uses Unity's assertion macros to verify any details required for the types involved.
The inputs:
- the
expectedvalue (as aconstversion of type being verified) - the
actualvalue (also as aconstversion of the desired type) - the
linethis function was called from (as typeUNITY_LINE_TYPE) - an optional
messageto be appended (as typeconst char*)
Inside the function, we use the internal versions of Unity's assertions to validate any details that need validating.
Let's look at an example! Let's say we have the following type:
typedef struct MyType_t
{
int a;
const char* b;
} MyType;
In our application, the length of b is supposed to be specified by a,
and b is therefore allowed to have any value (including 0x00).
Our custom assertion might look something like this:
void AssertEqualMyType(const MyType expected, const MyType actual, UNITY_LINE_TYPE line, const char* message)
{
//It's common to override the default message with our own
(void)message;
// Verify the lengths are the same, or they're clearly not matched
UNITY_TEST_ASSERT_EQUAL_INT(expected.a, actual.a, line, "Data length mismatch");
// Verify we're dealing with actual pointers
UNITY_TEST_ASSERT_NOT_NULL(expected.b, line, "Expected value should not be NULL");
UNITY_TEST_ASSERT_NOT_NULL(actual.b, line, "Actual value should not be NULL");
// Verify the string contents
UNITY_TEST_ASSERT_EQUAL_MEMORY(expected.b, actual.b, expected.a, line, "Data not equal");
}
There are a few things to note about this. First, notice we're using the
UNITY_TEST_ASSERT_ assertions? That's because these allow us to pass
on the specific line number. Second, notice we override the message with our
own more helpful messages? You don't need to do this, but anything you can do
to help a developer find a bug is a good thing.
What if there isn't an assertion that is right for your needs? You can
always do whatever operations are necessary yourself, and use UNITY_TEST_FAIL()
directly.
One final note: It's best to only test the things that are hard rules about how a type is supposed to work in your system. Anything else should be left to the test code.
For example, let's say that in our example above, there are situations where
it IS valid for the pointers to be NULL. Perhaps the pointers are ignored
completely when the a field is 0. In that case, we could drop those
assertions completely, or add logic to only check when necessary.
Similarly, should our assertion check that the length is positive? In this case, it's dangerous if it's negative, because the memory check wouldn't like it.
Updating for these concerns:
void AssertEqualMyType(const MyType expected, const MyType actual, UNITY_LINE_TYPE line, const char* message)
{
//It's common to override the default message with our own
(void)message;
// Verify the lengths are the same, or they're clearly not matched
UNITY_TEST_ASSERT_EQUAL_INT(expected.a, actual.a, line, "Data length mismatch");
// Verify the lengths are non-negative
UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT(0, expected.a, line, "Data length must be positive");
if (expected.a > 0)
{
// Verify we're dealing with actual pointers
UNITY_TEST_ASSERT_NOT_NULL(expected.b, line, "Expected value should not be NULL");
UNITY_TEST_ASSERT_NOT_NULL(actual.b, line, "Actual value should not be NULL");
// Verify the string contents
UNITY_TEST_ASSERT_EQUAL_MEMORY(expected.b, actual.b, expected.a, line, "Data not equal");
}
}
Once you have a function which does the main work, we need to create macros around it so that the assertion can be used conveniently both by CMock and directly in test code.
The macro that CMock requires is the UNITY_TEST_ASSERT_EQUAL_ form.
It starts with exactly that prefix, followed by the type name exactly as
declared, and takes four arguments:
#define UNITY_TEST_ASSERT_EQUAL_MyType(e,a,l,m) AssertEqualMyType(e,a,l,m)e- expected valuea- actual valuel- line number to report (filled in automatically by CMock)m- message to append at the end
CMock scans the helper header for macros matching this pattern and automatically uses them when it encounters the corresponding type.
It is also useful (though optional) to add the simpler TEST_ASSERT_EQUAL_
form so the assertion is easy to call directly inside your own test
functions:
#define TEST_ASSERT_EQUAL_MyType(e,a) \
UNITY_TEST_ASSERT_EQUAL_MyType(e,a,__LINE__,NULL)
#define TEST_ASSERT_EQUAL_MyType_MESSAGE(e,a,m) \
UNITY_TEST_ASSERT_EQUAL_MyType(e,a,__LINE__,m)With these in place, you can write TEST_ASSERT_EQUAL_MyType(expected, actual)
in your tests just like any built-in Unity assertion.
CMock needs to know which header file(s) contain your custom assertions.
Set the :unity_helper_path option in your CMock configuration to point
at the helper header:
:cmock:
:unity_helper_path:
- test/support/my_types_helper.hOr from Ruby:
CMock.new(unity_helper_path: ['test/support/my_types_helper.h'])
.setup_mocks('my_module.h')CMock parses each listed file, finds every UNITY_TEST_ASSERT_EQUAL_*
macro definition, and uses those macros automatically when it generates
mocks for parameters or return values of the matching types.
Done!
Bonus: Once you've created a custom assertion, you can use it
with :treat_as, just like any other standard type! This is
particularly useful when there is a custom type which is a pointer
to a custom type.
For example, let's say you have these types:
typedef struct MY_STRUCT_T_
{
int a;
const char* b;
} MY_STRUCT_T;
typedef MY_STRUCT_T* MY_STRUCT_POINTER_T;
Also, let's assume you've created the following assertion:
UNITY_TEST_ASSERT_EQUAL_MY_STRUCT_T(e,a,l,m)
You can use :treat_as like so:
:treat_as:
MY_STRUCT_POINTER_T: MY_STRUCT_T*
Finally, You can choose to avoid the use of _Expect calls altogether
for challenging types, and use a Callback instead. The advantage is that
you can fill in whichever assertions make sense for that particular test,
instead of needing to rely on reusable assertions as used elsewhere.
Typically, this option is also less work than option 3.