Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Mocking Function Pointers with CMock for Unit Testing with Ceedling

85 views
Skip to first unread message

Pranoti Joshi

unread,
Feb 6, 2025, 9:21:06 AMFeb 6
to ThrowTheSwitch Forums

Below is the sample function that I want to test using Ceedling:

fsp_err_t half_bridge_reset(const half_bridge_cfg_t* cfg, half_bridge_ctrl_t* ctrl, const ioport_instance_t* io)

{

/* Clear control block */

// memset(ctrl, 0, sizeof(half_bridge_ctrl_t));


/* Reset direction output */

io->p_api->pinWrite(io->p_ctrl, cfg->dir_pin, (bsp_io_level_t) HALF_BRIDGE_CONVERT_UP);


/* Clear duty cycle and return */

return cfg->pwm_tmr->p_api->dutyCycleSet(cfg->pwm_tmr->p_ctrl, 0, cfg->pwm_pin);

}


/** Timer API structure. General timer functions implemented at the HAL layer follow this API. */
typedef struct st_timer_api
{
    /** Sets the number of counts for the pin level to be high.  If the timer is counting, the updated duty cycle is
     * reflected after the next timer expiration.
     *
     *
     * @param[in]   p_ctrl             Control block set in @ref timer_api_t::open call for this timer.
     * @param[in]   duty_cycle_counts  Time until duty cycle should expire.
     * @param[in]   pin                Which output pin to update.  See implementation for details.
     */
    fsp_err_t (* dutyCycleSet)(timer_ctrl_t * const p_ctrl, uint32_t const duty_cycle_counts, uint32_t const pin);

    /** Stores timer information in p_info.
     *
     * @param[in]   p_ctrl     Control block set in @ref timer_api_t::open call for this timer.
     * @param[out]  p_info     Collection of information for this timer.
     */
    fsp_err_t (* infoGet)(timer_ctrl_t * const p_ctrl, timer_info_t * const p_info);

    /** Get the current counter value and timer state and store it in p_status.
     *
     * @param[in]   p_ctrl     Control block set in @ref timer_api_t::open call for this timer.
     * @param[out]  p_status   Current status of this timer.
     */
    fsp_err_t (* statusGet)(timer_ctrl_t * const p_ctrl, timer_status_t * const p_status);

    /** Specify callback function and optional context pointer and working memory pointer.
     *
     * @param[in]   p_ctrl                   Control block set in @ref timer_api_t::open call for this timer.
     * @param[in]   p_callback               Callback function to register
     * @param[in]   p_context                Pointer to send to callback function
     * @param[in]   p_working_memory         Pointer to volatile memory where callback structure can be allocated.
     *                                       Callback arguments allocated here are only valid during the callback.
     */
    fsp_err_t (* callbackSet)(timer_ctrl_t * const p_ctrl, void (* p_callback)(timer_callback_args_t *),
                              void const * const p_context, timer_callback_args_t * const p_callback_memory);

    /** Allows driver to be reconfigured and may reduce power consumption.
     *
     * @param[in]   p_ctrl     Control block set in @ref timer_api_t::open call for this timer.
     */
    fsp_err_t (* close)(timer_ctrl_t * const p_ctrl);
} timer_api_t;

/** This structure encompasses everything that is needed to use an instance of this interface. */
typedef struct st_timer_instance
{
    timer_ctrl_t      * p_ctrl;        ///< Pointer to the control structure for this instance
    timer_cfg_t const * p_cfg;         ///< Pointer to the configuration structure for this instance
    timer_api_t const * p_api;         ///< Pointer to the API structure for this instance
} timer_instance_t;


The function is accessing elements of the structure that are function pointers. How can we test such functions with Ceedling if we want to mock them?



Paul V

unread,
Feb 6, 2025, 5:01:48 PMFeb 6
to ThrowTheSwitch Forums
If you want to check the call of the function  io->p_api->pinWrite() , you could do following:

1. create a header file ioPin.h (or use an existing header) with the prototype corresponds to the function to be mocked - e.g. myPinWrtie(). Your test project sholdn't include an implementation of this function.
2. insert the include directive #include mock_ioPin.h in your test module. ceedling will create the mocks-set for the function myPinWrite() function.
3. allocate a variable of the type ioport_instance_t and a second variable of the same type as *(ioport_instance_t.p_api) in yout test function.
4. initialiaze the second variable:
    api_t apiInstance = { pinWrite = &myPinWrite, /* init other api_t fileds here if needed */ };
5. initialize : ioport_instance_t ioportInstance = { .p_api = &apiInstance,  /* init other  ioport_instance_t fileds here if needed */ };
6. set up the mocks for the function myPinWrite() corresponding your test case.
7. call the SuT-function:
    half_bridge_reset(..., &ioPortInstance);

On this call yout SuT will call the function myPinWrite(). That function will be implemented by CMock. CMock will also check whether the call of myPinWrite() does correspond to your mock setups. That's all.

Pranoti Joshi

unread,
Feb 12, 2025, 7:25:32 AMFeb 12
to ThrowTheSwitch Forums
The  io->p_api->pinWrite() refers here to the function pointer : 
/** Write specified level to a pin.

*

* @param[in] p_ctrl Pointer to control structure.

* @param[in] pin Pin to be written to.

* @param[in] level State to be written to the pin.

*/

fsp_err_t (* pinWrite)(ioport_ctrl_t * const p_ctrl, bsp_io_port_pin_t pin, bsp_io_level_t level);


How can I create mock for function pointer that is an element of the stucture.


Paul V

unread,
Feb 22, 2025, 6:36:57 PMFeb 22
to ThrowTheSwitch Forums
Sorry for late answer.

You don't need to create a mock - ceedling does it for you (see 1 and 2 in my previous post).
What you need is to assign the address of that mock function to a pointer, and that pointer is a structure member io->p_api->pinWrite. To do this, you does what I have described above (see 3 and 4 in my previous post).

In my previous answer:
- myPinWrite is the mocked function name (ceedling creates the code of this function for you).
- ioposrtInstance is the structure contains (the structure apiInstance contains) the pointer pinWrite.
- In your test code you pass the pointer &myPinWrite to the function half_bridge_reset() as the last argument.

Try this. If this does not work, publish your test code here - we will analyze it. Good luck.

Pranoti Joshi

unread,
Mar 19, 2025, 2:52:22 PMMar 19
to ThrowTheSwitch Forums
Adding more details here alongwith the test case I have written:

1. HeaderFile1.h
fsp_err_t R_IOPORT_PinWrite(ioport_ctrl_t * const p_ctrl, bsp_io_port_pin_t pin, bsp_io_level_t * p_pin_value);

2. HeaderFile2.h
    typedef struct st_ioport_api

{

    fsp_err_t (* pinWrite)(ioport_ctrl_t * const p_ctrl, bsp_io_port_pin_t pin, bsp_io_level_t level);

} ioport_api_t;


typedef struct st_ioport_instance

{

ioport_ctrl_t * p_ctrl;

ioport_cfg_t const * p_cfg;

ioport_api_t const * p_api;

} ioport_instance_t;


3. Mock_HeaderFile1.h (created by Ceedling)


#define R_IOPORT_PinWrite_ExpectAndReturn(p_ctrl, pin, level, cmock_retval) R_IOPORT_PinWrite_CMockExpectAndReturn(__LINE__, p_ctrl, pin, level, cmock_retval)
void R_IOPORT_PinWrite_CMockExpectAndReturn(UNITY_LINE_TYPE cmock_line, ioport_ctrl_t* const p_ctrl, bsp_io_port_pin_t pin, bsp_io_level_t level, fsp_err_t cmock_to_return);


4. SourceFile1.c


fsp_err_t half_bridge_reset(const half_bridge_cfg_t* cfg, half_bridge_ctrl_t* ctrl, const ioport_instance_t* io)

{

/* Reset direction output */

io->p_api->pinWrite(io->p_ctrl, cfg->dir_pin, (bsp_io_level_t) HALF_BRIDGE_CONVERT_UP);


return cfg->pwm_tmr->p_api->dutyCycleSet(cfg->pwm_tmr->p_ctrl, 0, cfg->pwm_pin);

}


5. Test_SourceFile1.c


#include "unity.h"
#include "half_bridge.h"
#include "mock_r_timer_api.h"
#include "mock_r_adc.h"
#include "mock_r_gpt.h"
#include "mock_r_ioport.h"

// Define test data
static half_bridge_cfg_t test_cfg;
static half_bridge_ctrl_t test_ctrl;
static ioport_instance_t test_io;
static ioport_api_t mock_ioport_api;

// Setup function: runs before each test
void setUp(void)
{
   
}

// Teardown function: runs after each test
void tearDown(void) {}

// Test case: Verify `half_bridge_reset` calls function pointers correctly
void test_half_bridge_reset(void)
{
    // Initialize the mock API function pointers
    mock_ioport_api.pinWrite = R_IOPORT_PinWrite_ExpectAndReturn;  // Mock function

    // Assign API structs to instances
    test_io.p_api = &mock_ioport_api;
   
    R_IOPORT_PinWrite_ExpectAndReturn(test_io.p_ctrl, test_cfg.dir_pin, HALF_BRIDGE_CONVERT_UP, FSP_SUCCESS);

    // Call the function under test
    fsp_err_t result = half_bridge_reset(&test_cfg, &test_ctrl, &test_io);

    // Check the return value
    TEST_ASSERT_EQUAL(FSP_SUCCESS, result);
}

With this implementation I am getting the below Error:

test/half_bridge/test_half_bridge.c: In function 'test_half_bridge_reset':
test/half_bridge/test_half_bridge.c:34:32: error: 'R_IOPORT_PinWrite_ExpectAndReturn' undeclared (first use in this function)
     mock_ioport_api.pinWrite = R_IOPORT_PinWrite_ExpectAndReturn;  // Mock function
                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test/half_bridge/test_half_bridge.c:34:32: note: each undeclared identifier is reported only once for each function it appears in
ERROR: Shell command failed.
Message has been deleted

Paul V

unread,
Mar 27, 2025, 6:22:55 PMMar 27
to ThrowTheSwitch Forums
1. On your screenshot: The variable mock_ioport_api is defined and used in the file Test_SourceFile1.c. The comilier log with the error message shows that you compile the file test/half_bridge/test_half_bridge.c. Is it the same file?
   If not (and I assume that your test code lies in the test/half_bridge/test_half_bridge.c) - check whether the true test file contains definition of the variable mock_ioport_api.

2. Your test file must contain follwing preprocessor directive:
   #include "mock_HeaderFile1.h"
   (if you really use this file for declaration of the function R_IOPORT_PinWrite().

3. You need include the mock_xyz.h header only if you test code will use the mocks of some functions defined in the header xyz.h.
    E.g.: You want to have the mocks for the function R_IOPORT_PinWrite(). This function is declared in the header HeaderFile1.h. Therefore you need #include "mock_HeaderFile1.h" in your test file.
   Are you sure that you want to mock some functions defined in the headers r_timer_api.h,  r_adc.h, r_gpt.h and r_ioport.h ? If not, you don't need include the corresponding mock_*.h.

4. in the code on your screenshot (Test_SourceFile1.c) are some variables (test_cfg, test_ctrl) not initialized but used. Is the initializing omitted for simplifying of the screenshot? It must be done in the real code.
   E.g. the line
       R_IOPORT_PinWrite_ExpectAndReturn(test_io.p_ctrl, test_cfg.dir_pin, HALF_BRIDGE_CONVERT_UP, FSP_SUCCESS);
   declares that your expectation is: Due to call of the function under test the mock-function  R_IOPORT_PinWrite() gets the value  test_cfg.dir_pin as the second parameter pin. But the static variable  test_cfg.dir_pin is not initialized. Of cause, this variable contains some value (as static variable - zero), but I'm not sure it's waht you really want.
  
   If you want only check whether the mocked function is called and the arguments don't matter, use R_IOPORT_PinWrite_IgnoreAndReturn( FSP_SUCCESS); instead.
   If you want to check the values passed into mocked function too, use somthing as (this is not the whole code!) :

        testio_cfg.dir_pin = TEST_PIN; // TEST_PIN should be a valid value of a pin
        R_IOPORT_PinWrite_ExpectAndReturn(test_io.p_ctrl,  TEST_PIN, HALF_BRIDGE_CONVERT_UP, FSP_SUCCESS);

        // Call the function under test
        fsp_err_t result = half_bridge_reset(&test_cfg, &test_ctrl, &test_io);

    So you will check whether you function under test really uses the pin value from the argument passed on the call.
    And you should executes such test with at least two different values of the pin - to check whether you code don't use accidentaly the CONSTANT equal to the test value TEST_PIN...
Reply all
Reply to author
Forward
0 new messages