Article summary
I recently started on a large project that utilizes the powerful CMock mocking library in its testing. I tried to find some simple tutorials to get a better understanding of this library. But though there’s a lot of helpful documentation on CMock, I found little advice on getting to know it in general. So here is my quick tutorial on how to get started with CMock.
CMock Overview
CMock is an automated stub and mock generation framework made by a community of C developers, including Atom Greg Williams. It works within the Unity testing framework, and it is automatically included in any projects that use the Ceedling build management tool.
CMock autogenerates all functions defined in your code’s header files. It not only generates them, but it gives you additional functionality around those calls.
- Expectations allow you to specify functions that you expect to be called with specific values by the function under test.
- Returns allow you to specify what data the mocked function should return to the tested code.
- Ignores allow you to tell the code to ignore values from expectations, if they’re not relevant to the test.
- Callbacks allow you to specify more complex mocked functionality.
The power of CMock really shines when using it. Let’s get ready to watch it work!
Let’s Spin Up a Project!
Install Ceedling
To get started, we’re first going to install Ceedling using the command
gem install ceedling
Then generate a new Ceedling project with
ceedling new cmock_example
Make testable code
In order to write tests, we need testable code. For this example, I created two simple classes. The first is a rectangle
class that takes in height
and width
values and a few basic functions. The second is a shape_container
class that creates a rectangle
and operates on it.
typedef struct rectangle
{
uint16_t r_length;
uint16_t r_width;
} rectangle_t;
static rectangle_t rect;
void rectangle_init(uint16_t length, uint16_t width)
{
rect.r_length = length;
rect.r_width = width;
}
uint16_t rectangle_get_area(void)
{
return rect.r_width * rect.r_length;
}
uint16_t rectangle_get_perimeter(void)
{
return (rect.r_length + rect.r_width) * 2;
}
bool rectangle_is_square(void)
{
return (rect.r_length == rect.r_width);
}
void shape_container_init(uint16_t r_length, uint16_t r_width)
{
rectangle_init(r_length, r_width);
}
bool shape_container_calc_rect(uint16_t r_length, uint16_t r_width)
{
rectangle_init(r_length, r_width);
rectangle_get_area();
rectangle_get_perimeter();
return rectangle_is_square();
}
Write the first test
Now that we have testable code, we want to write a test to make sure it works as we expect. To test the shape_conductor_init
function, we’ll need to mock out that call to rectangle_init
. To do that, we’ll use CMock.
In a new file called test_shape_container
, we add our imports.
#include "unity.h" // The testing framework
#include "shape_container.h" // The header for the code we are testing
#include "mock_rectangle.h" // A mock header
The last header is the most interesting one. I have header.h
in my codebase, but currently mock_header.h
does not exist. But once I run my test suite, CMock will generate that header, as well as a full mock_rectangle.c
that I can use in my tests.
Next, we want to write our actual test.
void test_shape_container_init(void)
{
// Set up known values
uint16_t length = 4;
uint16_t width = 3;
//State, in order of call, what expectations we have, and the expected values to be returned, if any
rectangle_init_Expect(length, width);
//Run Actual Function Under Test
shape_container_init(length, width);
}
Here we see CMock’s Expectations in action. When this test runs, Unity will expect shape_container_init
to call rectangle_init
with the specified values. If this does not happen, the test will fail.
To run this test, I run ceedling
from the root of the project directory and get this output.
~ ceedling
Test 'test_shape_container.c'
-----------------------------
Compiling shape_container.c...
Linking test_shape_container.out...
Running test_shape_container.out...
--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 1
PASSED: 1
FAILED: 0
IGNORED: 0
The test passes! But that is an easy one. What if we want to verify that multiple functions are called? Or that the shape_container
returns the correct one once it’s received data from the rectangle
?
We can test that, too! Let’s write a test for shape_container_calc_rect
and verify that it returns the same value that rectangle_is_square
returns.
void test_shape_container_calc_rect_is_square(void)
{
// Set up known values
uint16_t length = 4;
uint16_t width = 4;
uint16_t x_area = length * width;
uint16_t x_perimeter = length + width + length + width;
bool x_is_square = true;
//State, in order of call, what expectations we have, and the expected values to be returned, if any
rectangle_init_Expect(length, width);
rectangle_get_area_ExpectAndReturn(x_area);
rectangle_get_perimeter_ExpectAndReturn(x_perimeter);
rectangle_is_square_ExpectAndReturn(x_is_square);
//Run Actual Function Under Test
bool is_square = shape_container_calc_rect(length, width);
//We can still verify whatever things we normally would after
TEST_ASSERT_EQUAL(x_is_square, is_square);
}
void test_shape_container_calc_rect_is_not_square(void)
{
// Set up known values
uint16_t length = 4;
uint16_t width = 3;
uint16_t x_area = length * width;
uint16_t x_perimeter = length + width + length + width;
bool x_is_square = false;
//State, in order of call, what expectations we have, and the expected values to be returned, if any
rectangle_init_Expect(length, width);
rectangle_get_area_ExpectAndReturn(x_area);
rectangle_get_perimeter_ExpectAndReturn(x_perimeter);
rectangle_is_square_ExpectAndReturn(x_is_square);
//Run Actual Function Under Test
bool is_square = shape_container_calc_rect(length, width);
//We can still verify whatever things we normally would after
TEST_ASSERT_EQUAL(x_is_square, is_square);
}
We’ve added two tests, one testing that the function returns true
, and the other for returning false
. In the test, we list our expectations in order that the functions will be called. Any other order results in a failing test. We also capture the output from our function under test and call a separate assertion on it to check its value.
Wrapping Up
Now you know everything you need to know to get started writing tests using CMock! This is just a fraction of the functionality that CMock provides. It is a powerful tool that can dramatically increase the speed at which you write unit tests in C. Now get out there and start mocking!
The code from this example can be found on my GitHub account.
shape_conductor_init -> shape_container_init
It’s very confusing trying to understand typos in code, when unfamiliar with what is being generated automatically by a library.
Thanks for your guide!
Thank you for the CMock example. I have a dumb question: Your test just outputs the number of PASS/FAILS:
OVERALL TEST SUMMARY
——————–
TESTED: 4
PASSED: 4
FAILED: 0
IGNORED: 0
Run the rake command in test folder, I get this:
TestCMock.c:21:test_MemNewWillReturnNullIfGivenIllegalSizes:PASS
TestCMock.c:33:test_MemShouldProtectAgainstMemoryOverflow:PASS
etc…
How do I force the itemized output?