SRehman
SRehman

Reputation: 469

Can gmock be used for stubbing C functions?

I am new to gmock, so I want to know how can I stub simple C function called in a function under test for Unit Testing.

Example:

int func(int a)
{
  boolean find;
  // Some code
  find = func_1();
  return find;
}

I have searched about gmock and in my understanding gmock does not provide functionality to stub simple C functions, therefore I want to ask does gmock provides functionality to mock or stub func_1?

If not how can I stub func_1 manually in my test code without changing source code? I am using google test framework for unit testing.

Thanks.

Upvotes: 34

Views: 47480

Answers (8)

Edson Manoel
Edson Manoel

Reputation: 108

After struggling because some of the answers here did not exactly work for me I came up with my own solution.

The external library that we will mock in this example is the ESP-IDF gpio library. Imagine we have written a function to configure a GPIO of ESP-32 and another one to toggle a GPIO, both in the same file togle_gpio.c file:

/**
* @file toggle_gpio.c
* @author Edson Manoel da Silva (https://www.linkedin.com/in/edsonms/)
* @brief Functions to toggle gpio
* @version 0.1
* 
* @copyright Copyright (c) 2023
* 
*/
#include "toggle_gpio.h"
#include <stdio.h>
#include "esp_system.h"
#include "esp_timer.h"
#include "driver/gpio.h"

esp_err_t ConfigureGPIO(gpio_num_t PinNumber) 
{
    esp_err_t ResponseStatus;
    gpio_config_t io_conf;

    //Config the pin
    io_conf.pin_bit_mask = (1ULL << PinNumber);
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    ResponseStatus = gpio_config(&io_conf);

    //Check if config was ok and return its status if not
    if (ESP_OK != ResponseStatus)
        return (ResponseStatus);
    
    //If config was ok, set initial level too LOW
    ResponseStatus = gpio_set_level(PinNumber, LOW);
    
    return (ResponseStatus);
}

esp_err_t ToggleGPIO(gpio_num_t PinNumber) 
{
        static uint8_t PinLastState = LOW;
        esp_err_t ResponseStatus;

        PinLastState = ~PinLastState;
        ResponseStatus = gpio_set_level(PinNumber,PinLastState);  // Toggle pin

        return (ResponseStatus);
}

As you can see, we have dependency of ESP-IDF functions gpio_config and gpio_set_level. How can we test this functions without having a ESP-32 hardware in hands and without compiling the external gpio library? First of all, we need to create mock versions of the gpio functions:

/**
* @file gpio_mock.cpp
* @author Edson Manoel da Silva (https://www.linkedin.com/in/edsonms/)
* @brief Mock ESP-IDF gpio functions
* @version 0.1
* @date 2023-07-29
* 
* @copyright Copyright (c) 2023
* 
*/

#include "gpio_mock.hpp"
#include "driver/gpio.h"

gpioMock* gpioMockObj;

//Create mock functions definitions for link-time replacement
extern "C"
{
    esp_err_t gpio_config(const gpio_config_t *pGPIOConfig)
    {
        return gpioMockObj->gpio_config(pGPIOConfig);
    }
    esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level)
    {
        return gpioMockObj->gpio_set_level(gpio_num,level);
    }
}

Those will be the actual functions to be called when we test our toggle_gpio.c file. But for that to work, you need to create the Mock objects:

/**
* @file gpio_mock.hpp
* @author Edson Manoel da Silva (https://www.linkedin.com/in/edsonms/)
* @brief Mock ESP-IDF gpio functions
* @version 0.1
* 
* @copyright Copyright (c) 2023
* 
*/

#pragma once

#include <gmock/gmock.h>
#include "driver/gpio.h"

//create the MOCKS
class gpioFunctions
{
    public:
        virtual ~gpioFunctions(){}
        virtual esp_err_t gpio_config(const gpio_config_t *pGPIOConfig) = 0;
        virtual esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level) = 0;
};

class gpioMock : public gpioFunctions
{
    public:
        MOCK_METHOD(esp_err_t,gpio_config,(const gpio_config_t *pGPIOConfig),(override));
        MOCK_METHOD(esp_err_t, gpio_set_level,(gpio_num_t gpio_num, uint32_t level),(override));
};

//create globl mock object
extern gpioMock* gpioMockObj;

Now, you are good to write your test and tell your gpio_config and gpio_set_level doubles how they need to behave and what to expect from them! You can write as much tests as needed to catch all the situations of iteration between our embedded C code and the external functions or the external hardware.

/**
* @file test_gpio.cpp
* @author Edson Manoel da Silva (https://www.linkedin.com/in/edsonms/)
* @brief Test toggle GPIO function
* @version 0.1
* 
* @copyright Copyright (c) 2023
* 
*/

#include <gtest/gtest.h>
#include <gmock/gmock.h> 
#include "gpio_mock.hpp"

#include <iostream>


using ::testing::_;
using ::testing::Return;
using namespace std;

namespace GpioTests
{
    //System under test
    #include "toggle_gpio.c"

    class GpioAPIsTest : public ::testing::Test
    {
    protected:

        void SetUp() override
        {
            //Set up mock object
            gpioMockObj = new gpioMock;
        }

        void TearDown() override
        {
            //Clean up the mock object used
            delete gpioMockObj;
        }
    };


    TEST_F(GpioAPIsTest,TestConfigureGPIO)
    {
        //List of tests to be done for function TestConfigureGPIO
        //1 - Test returning success from gpio_config and gpio_set_level
        //2 - Test returning error from gpio_config
        //3 - Test returning error from gpio_set_level

        bool TestHasError = 0;
        esp_err_t test_return = ESP_ERR_INVALID_ARG;

        //////////////////////////////////////////////// Test 1 /////////////////////////////////////////////////////////////////////////////////////
        cout << endl << "[   INFO   ] Test 1 - Test returning success from gpio_config and gpio_set_level" << endl;
        
        EXPECT_CALL(*gpioMockObj,gpio_config(_)) //The called argument is a pointer, so we will test call expectation with the any argument pararameter
        .Times(1)
        .WillOnce(Return(ESP_OK));
        
        EXPECT_CALL(*gpioMockObj,gpio_set_level(GPIO_NUM_2,LOW))
        .Times(1)
        .WillOnce(Return(ESP_OK));

        test_return = ConfigureGPIO(GPIO_NUM_2);
        
        EXPECT_EQ(ESP_OK, test_return) << (TestHasError = true); //If expectation fails, set TestHasError to true;
        if (TestHasError)
            std::cout << "[   FAIL   ] " << "Test 1 fail" << endl;
        else
            std::cout << "[    OK    ] " << "Test 1 pass" <<endl;         

        //////////////////////////////////////////////// Test 2 /////////////////////////////////////////////////////////////////////////////////////
        TestHasError = 0;
        test_return = ESP_OK;
        cout << endl << "[   INFO   ] Test 2 - Test returning error from gpio_config" << endl;

        EXPECT_CALL(*gpioMockObj,gpio_config(_)) //The called argument is a pointer, so we will test call expectation with the any argument pararameter
        .Times(1)
        .WillOnce(Return(ESP_ERR_INVALID_ARG));

        test_return = ConfigureGPIO((gpio_num_t)(GPIO_NUM_38));
        
        EXPECT_EQ(ESP_ERR_INVALID_ARG, test_return) << (TestHasError = true); //If expectation fails, set TestHasError to true;
        if (TestHasError)
            std::cout << "[   FAIL   ] " << "Test 2 fail" << endl;
        else
            std::cout << "[    OK    ] " << "Test 2 pass" <<endl;     

        //////////////////////////////////////////////// Test 3 /////////////////////////////////////////////////////////////////////////////////////
        TestHasError = 0;
        test_return = ESP_OK;
        cout << endl << "[   INFO   ] Test 3 - Test returning error from gpio_set_level" << endl;

        EXPECT_CALL(*gpioMockObj,gpio_config(_)) //The called argument is a pointer, so we will test call expectation with the any argument pararameter
        .Times(1)
        .WillOnce(Return(ESP_OK));
        
        EXPECT_CALL(*gpioMockObj,gpio_set_level(GPIO_NUM_39,LOW))
        .Times(1)
        .WillOnce(Return(ESP_ERR_INVALID_ARG));

        test_return = ConfigureGPIO(GPIO_NUM_39);
       
        EXPECT_EQ(ESP_ERR_INVALID_ARG, test_return) << (TestHasError = true); //If expectation fails, set TestHasError to true;
        if (TestHasError)
            std::cout << "[   FAIL   ] " << "Test 3 fail" << endl;
        else
            std::cout << "[    OK    ] " << "Test 3 pass" <<endl << endl;       
    
    }

    TEST_F(GpioAPIsTest, TestToggleGpio)
    {
    //List of tests to be done for function ToggleGpio
    //1 - Test toggling from LOW to HIGH   
    //2 - Test toggling from HIGH to LOW
    //3 - Test gpio_set_level returning error

        bool TestHasError = 0;
        esp_err_t test_return = ESP_ERR_INVALID_ARG;

        //////////////////////////////////////////////// Test 1 /////////////////////////////////////////////////////////////////////////////////////
        cout << endl << "[   INFO   ] Test 1 - Test toggling pin from initial state LOW to HIGH" << endl;    
        
        EXPECT_CALL(*gpioMockObj,gpio_set_level(GPIO_NUM_2,HIGH))
        .Times(1)
        .WillOnce(Return(ESP_OK));

        test_return = ToggleGPIO(GPIO_NUM_2);

        EXPECT_EQ(ESP_OK,test_return) << (TestHasError = true); //If expectation fails, set TestHasError to true;
        if (TestHasError)
            std::cout << "[   FAIL   ] " << "Test 1 fail" << endl;
        else
            std::cout << "[    OK    ] " << "Test 1 pass" <<endl << endl;  

        //////////////////////////////////////////////// Test 2 /////////////////////////////////////////////////////////////////////////////////////
        cout << endl << "[   INFO   ] Test 2 - Test toggling pin from HIGH to LOW" << endl;   

        EXPECT_CALL(*gpioMockObj,gpio_set_level(GPIO_NUM_2,LOW))
        .Times(1)
        .WillOnce(Return(ESP_OK));

        test_return = ToggleGPIO(GPIO_NUM_2);

        EXPECT_EQ(ESP_OK,test_return) << (TestHasError = true); //If expectation fails, set TestHasError to true;
        if (TestHasError)
            std::cout << "[   FAIL   ] " << "Test 2 fail" << endl;
        else
            std::cout << "[    OK    ] " << "Test 2 pass" <<endl << endl;  

        //////////////////////////////////////////////// Test 3 /////////////////////////////////////////////////////////////////////////////////////
        cout << endl << "[   INFO   ] Test 3 - Test toggling pin return error" << endl;   
        EXPECT_CALL(*gpioMockObj,gpio_set_level(GPIO_NUM_2,HIGH))
        .Times(1)
        .WillOnce(Return(ESP_ERR_INVALID_ARG));

        test_return = ToggleGPIO(GPIO_NUM_2);        

        EXPECT_EQ(ESP_ERR_INVALID_ARG,test_return) << (TestHasError = true); //If expectation fails, set TestHasError to true;
        if (TestHasError)
            std::cout << "[   FAIL   ] " << "Test 3 fail" << endl;
        else
            std::cout << "[    OK    ] " << "Test 3 pass" <<endl << endl;  

    }
}

This example with all the files and folder structure:

https://github.com/edsonms/GoogleMockWithEmbeddedC/tree/master

Upvotes: 0

Th2mas
Th2mas

Reputation: 11

This might not totally fit your case, but if you find yourself writing C code that needs to be stubbed (e.g. you want to stub some I/O connection), you could use function pointers.

So let's say you have a header and source file with the following function declaration and definition:

some_file.h

// BEGIN SOME_FILE_H
#ifndef SOME_FILE_H
#define SOME_FILE_H

#include <stdbool.h>

bool func_1(void);

#endif // SOME_FILE_H
// END SOME_FILE_H

some_file.c

// BEGIN SOME_FILE_C
#include "some_file.h"

bool func_1(void) {
  return true;
}
// END SOME_FILE_C

Now, if you would like to stub this method, all you have to do is convert this method into a function pointer. You will also have to adjust the .c file, since we changed the function and made it a function pointer.

some_file.h

// BEGIN SOME_FILE_H
#ifndef SOME_FILE_H
#define SOME_FILE_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>

extern bool (*func_1)(void);

#ifdef __cplusplus
}
#endif

#endif // SOME_FILE_H
// END SOME_FILE_H

some_file.c

// BEGIN SOME_FILE_C
#include "some_file.h"

// Make the method static, so that it cannot be accessed anywhere else except in this file
static bool func_1_Impl(void) {
    return true;
}
bool (*func_1)(void) = func_1_Impl;
// END SOME_FILE_C

You don't have to adjust the function calls anywhere else, since func_1 will simply be redirected to func_1_Impl.

Now for stubbing this method:

In your *_test.cc file (or whatever you call your test file), you can create a mock class with the same interface as some_file.h. You can then overwrite the function pointer with the defined mock function.

some_file_test.cc

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "some_file.h"
#include "header_where_func_is_declared.h"

using ::testing::AtLeast;

class SomeFile {
public:
    virtual bool func1() = 0;
};

class MockSomeFile : SomeFile {
public:
    MOCK_METHOD(bool, func1, (), (override));
};

TEST(Func1Test, ShouldMockStuff) {
    // Arrange
    auto expected = 0; // or whatever the expected result is

    // Define the mock object to be used
    static MockSomeFile mock;

    // The important part: Overwrite the function pointer (with a lambda function)
    func_1 = []() { return mock.func1(); };

    // Define the call expectations
    EXPECT_CALL(mock, func1)
            .Times(AtLeast(1));

    // Act
    auto actual = func();

    // Assert
    EXPECT_EQ(expected, actual);
}

This test should pass, showing that the mocking worked. You can also check, whether the test will fail, if you change the EXPECT_CALL call, e.g. set .Times(AtLeast(2)).

Note: You might see that the adjusted test passed with AtLeast(2), although this is wrong. You should still see the correct error message in the console.

I hope this helps you and everyone else who has a similar problem!

Upvotes: 0

Georg P.
Georg P.

Reputation: 3164

I found myself in the same situation lately. I had to write unit tests for libraries written in C, which in turn had dependencies to other libraries also written in C. So I wanted to mock all function calls of dependencies using gmock. Let me explain my approach by an example.

Assume the code to be tested (library A) calls a function from another library, lib_x_function():

lib_a_function()
{
   ...
   retval = lib_x_function();
   ...
}

So, I want to mock the library X. Therefore I write an interface class and a mock class in a file lib_x_mock.h:

class LibXInterface {
public:
   virtual ~LibXInterface() {}
   virtual int lib_x_function() = 0;
}

class LibXMock : public LibXInterface {
public:
   virtual ~LibXMock() {}
   MOCK_METHOD0(lib_x_function, int());
}

Additionally I create a source file (say, lib_x_mock.cc), that defines a stub for the actual C function. This shall call the mock method. Note the extern reference to the mock object.

#include lib_x.h
#include lib_x_mock.h
extern LibXMock LibXMockObj;    /* This is just a declaration! The actual
                                   mock obj must be defined globally in your
                                   test file. */

int lib_x_function()
{
    return LibXMockObj.lib_x_function();
}

Now, in the test file, which tests the library A, I must define the mock object globally, so that it is both reachable within your tests and from lib_x_mock.cc. This is lib_a_tests.cc:

#include lib_x_mock.h

LibXMock LibXMockObj;  /* This is now the actual definition of the mock obj */

...
TEST_F(foo, bar)
{
   EXPECT_CALL(LibXMockObj, lib_x_function());
   ...
}

This approach works perfectly for me, and I have dozens of tests and several mocked libraries. However, I have a few doubts if it is ok to create a global mock object - I asked this in a separate question and still wait for answers. Besides this I'm happy with the solution.


UPDATE: The problem about the global object can be easily remedied by creating the object e.g. in the constructor of the test fixture, and just storing a pointer to that object in a global variable.

However, also note my alternative answer to this question, that I just posted.

Upvotes: 33

MrDor
MrDor

Reputation: 108

You can use the Cutie library to mock C function GoogleMock style.
There's a full sample in the repo, but just a taste:

INSTALL_MOCK(close);
CUTIE_EXPECT_CALL(fclose, _).WillOnce(Return(i));

Upvotes: 2

Georg P.
Georg P.

Reputation: 3164

This is another answer of mine to this question. In the two years that passed since the first answer, I came to understand that GMock is simply the wrong framework for mocking C functions. In situations where you have a lot of functions to mock, my previously posted answer is simply too cumbersome. The reason is that GMock uses Object Seams to replace production code with mock code. This relies on polymorphic classes, which don't exist in C.

Instead, to mock C functions, you should use Link Seams, which replace the production code with the mock code at link time. Several frameworks exist for this purpose, but my favorite one is the Fake Function Framework (FFF). Check it out, it's a lot simpler than GMock. It also works perfectly well in C++ applications.

For the interested, here is a good article by Michael Feathers about the different seam types.

Upvotes: 41

Old Fox
Old Fox

Reputation: 8725

In each UT we are trying to verify a specific behavior.

You should fake something when it's very hard/impossible(we need to isolate our unit)/spend a lot of time(running time..) to simulate a specific behavior.

Using a 'C' function in the explicit way means that the function is apart of your unit(therefore you shouldn't mock it..). In this answer I explain the initiative to test the method as is(in the edit..). In my opinion you should call func with the parameters which cause func_1 to simulate the behavior you want to verify.

GMock is based on compilation fake(macros), therefore you cannot do such a thing. To fake 'C' methods you have to use a different tools such as Typemock Isolator++.

If you don't want to use Isolator++, then you should refactor your method; Change func to func(int a, <your pointer the function>) and then use the pointer instead of func_1.

My chart in this answer might help to decide the way to handle your case.

Upvotes: 0

matthandi
matthandi

Reputation: 111

I was looking already a long time for a solution to mock legacy c-functions with googleMock without changing existing code and last days I found the following really great article: https://www.codeproject.com/articles/1040972/using-googletest-and-googlemock-frameworks-for-emb

Today I wrote my first unit test for c-functions using gmock and took as example two functions from the bcm2835.c library (http://www.airspayce.com/mikem/bcm2835/) for raspberry Pi programming: Here is my solution: I'm using the gcc 4.8.3. under Eclipse and Windows. Be Aware to set the Compiler option -std=gnu++11.

Here are my functions to be tested

int inits(void);
void pinMode(uint8_t pin, uint8_t mode);

int inits(){
    return bcm2835_init();
}

void pinMode(uint8_t pin, uint8_t mode){
    bcm2835_gpio_fsel(pin, mode);
}

Includes and defines for unit testing with googleTest / googleMock

// MOCKING C-Functions with GMOCK :)
#include <memory>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
using namespace ::testing;
using ::testing::Return;

Mock BCM2835Lib functions

class BCM2835Lib_MOCK{
public:
    virtual ~BCM2835Lib_MOCK(){}

    // mock methods
    MOCK_METHOD0(bcm2835_init,int());
    MOCK_METHOD2(bcm2835_gpio_fsel,void(uint8_t,uint8_t));
};

Create a TestFixture

class TestFixture: public ::testing::Test{
public:
    TestFixture(){
        _bcm2835libMock.reset(new ::testing::NiceMock<BCM2835Lib_MOCK>());
    }
    ~TestFixture(){
        _bcm2835libMock.reset();
    }
    virtual void SetUp(){}
    virtual void TearDown(){}

    // pointer for accessing mocked library
    static std::unique_ptr<BCM2835Lib_MOCK> _bcm2835libMock;
};

Instantiate mocked lib functions

// instantiate mocked lib
std::unique_ptr<BCM2835Lib_MOCK> TestFixture::_bcm2835libMock;

Fake lib functions to connect Mocks with the c-functions

// fake lib functions
int  bcm2835_init(){return TestFixture::_bcm2835libMock->bcm2835_init();}
void bcm2835_gpio_fsel(uint8_t pin, uint8_t mode){TestFixture::_bcm2835libMock->bcm2835_gpio_fsel(pin,mode);}

Create unit testing class for BCM2835 from TestFixture

// create unit testing class for BCM2835 from TestFixture
class BCM2835LibUnitTest : public TestFixture{
public:
    BCM2835LibUnitTest(){
        // here you can put some initializations
    }
};

Write the Tests using googleTest and googleMock

TEST_F(BCM2835LibUnitTest,inits){
    EXPECT_CALL(*_bcm2835libMock,bcm2835_init()).Times(1).WillOnce(Return(1));

    EXPECT_EQ(1,inits()) << "init must return 1";
}

TEST_F(BCM2835LibUnitTest,pinModeTest){

    EXPECT_CALL(*_bcm2835libMock,bcm2835_gpio_fsel( (uint8_t)RPI_V2_GPIO_P1_18
                                                   ,(uint8_t)BCM2835_GPIO_FSEL_OUTP
                                                  )
               )
               .Times(1)
               ;

    pinMode((uint8_t)RPI_V2_GPIO_P1_18,(uint8_t)BCM2835_GPIO_FSEL_OUTP);
}

Results :)

[----------] 2 tests from BCM2835LibUnitTest
[ RUN      ] BCM2835LibUnitTest.inits
[       OK ] BCM2835LibUnitTest.inits (0 ms)
[ RUN      ] BCM2835LibUnitTest.pinModeTest
[       OK ] BCM2835LibUnitTest.pinModeTest (0 ms)
[----------] 2 tests from BCM2835LibUnitTest (0 ms total)

Hope it will help :) - for me this is a really working solution.

Upvotes: 11

amir
amir

Reputation: 51

I had a similar case in a project I was unit-testing. My solution was to create two make files, one for production and one for testing.

If the function func_1() is definded in the header a.h, and implemented in a.cpp, then for testing you can add a new source file a_testing.cpp, that will implement all the functions in a.h as a stub. For unittesting, just compile and link with a_testing.cpp instead of a.cpp and the tested code will call your stub.

In a_testing.cpp you can then forward the call to a gmock object that will set expectations and actions as usual based on the state and paramteres.

I know it's not perfect, but it works ans solve the problem without changing production code or interfaces at all.

Upvotes: 2

Related Questions