Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock only some specific functions in the same file (partial mocking) #936

Open
parmi93 opened this issue Oct 9, 2024 · 9 comments
Open

Comments

@parmi93
Copy link

parmi93 commented Oct 9, 2024

I know this topic has been discussed at length, and several workaround have already been presented, but none of the existing solutions satisfy me fully, and I think Ceedling can do better(?) (although I am not sure that my proposal is feasible).

The workaround that is usually proposed is to move the static functions into private helper file and include it in the main file, so the helper file can be tested separately, and then use the "mocked version" of the helper file when you want to test the main file.
But sometimes this is not possible for example when working with legacy code, also if a helper function calls another helper function I have to separate these functions into different files (which I don't like at all), and in any case sometimes I want to keep my static functions in the main file because it makes sense according to the design of my application.

I would like to propose the following solution:

// my_file.c
static int prv_a, prv_b, prv_c;
static int prv_n;

static void prv_foo_N(int n)
{
    prv_n = n;
}

static void prv_foo_AB(int a, int b)
{
    prv_a = a;
    prv_b = b;
    prv_foo_N(a + b);
}

void bar(int a, int b, int c)
{
    prv_c = c;
    prv_foo_AB(a, b);
}
// test_bar_fn.c

/* Tell Ceedling that I want the file "my_file.c" to be included directly in
 * this test file.
 * So Ceedling could clone the file "my_file.c" into a file with the following
 * name "build/test/original_src/src_<this_test_file_name>.c", and then replace
 * this macro with: 
 * #include "build/test/original_src/src_test_bar_fn.c" (yes, .c) */
INCLUDE_SOURCE("my_file.c");

/* Tell Ceedling to literally remove the prv_foo_AB() function from the 
 * "build/test/original_src/src_test_bar_fn.c" file, and use the "mocked version"
 * of it.
 * The "mock version" of prv_foo_AB() could be created in a file with the 
 * following name "build/test/partial_mocks/mock_my_file_c_<function_name>.c"
 * which contain only the "mocked version" of prv_foo_AB() function, and then
 * replace this macro with:
 * #include "build/test/partial_mocks/mock_my_file_c_prv_foo_AB.c" (yes, .c) */
MOCK_FUNCTION("prv_foo_AB");

void test_bar()
{
    // Function defined in build/test/partial_mocks/mock_my_file_c_prv_foo_AB.c
    prv_foo_AB_Expect(2, 8, 99);

    // Function defined in build/test/original_src/src_test_bar_fn.c
    bar(2, 9, 99);
    
    TEST_ASSERT_EQUAL_INT(99, prv_n);
}
// test_prv_foo_AB_fn.c

/* Tell Ceedling that I want the file "my_file.c" to be included directly in
 * this test file.
 * So Ceedling could clone the file "my_file.c" into a file with the following
 * name "build/test/original_src/src_<this_test_file_name>.c", and then replace
 * this macro with: 
 * #include "build/test/original_src/src_test_prv_foo_AB_fn.c" (yes, .c) */
INCLUDE_SOURCE("my_file.c");

/* Tell Ceedling to literally remove the prv_foo_N() function from the
 * "build/test/original_src/src_test_prv_foo_AB_fn.c" file, and use the
 * "mocked version" of it.
 * The "mock version" of prv_foo_N() could be created in a file with the
 * following name "build/test/partial_mocks/mock_my_file_c_<function_name>.c"
 * which contain only the "mocked version" of prv_foo_N() function, and then
 * replace this macro with:
 * #include "build/test/partial_mocks/mock_my_file_c_prv_foo_N.c" (yes, .c) */
MOCK_FUNCTION("prv_foo_N");

void test_prv_foo_AB()
{
    // Function defined in build/test/partial_mocks/mock_my_file_c_prv_foo_N.c
    prv_foo_N_Expect(10);

    // Function defined in build/test/original_src/src_test_prv_foo_AB_fn.c
    prv_foo_AB(2, 8);

    TEST_ASSERT_EQUAL_INT(2, prv_a);
    TEST_ASSERT_EQUAL_INT(8, prv_b);
}
// test_prv_foo_N_fn.c

/* In this specific case this macro could simply be replaced with:
 * #include "my_file.c" (yes, .c), so including the original source file 
 * without cloning it, because no MOCK_FUNCTION() marco is used in this test
 * file. */
INCLUDE_SOURCE("my_file.c")

void test_prv_foo_N()
{
    // Function defined in my_file.c
    prv_foo_N(10); 

    TEST_ASSERT_EQUAL_INT(10, prv_n);
}

Basically INCLUDE_SOURCE("file_name.c") tells Ceedling to copy the file file_name.c into build/test/original_src/src_<file_name>.c, so the original_src folder should contain a copy of the original source files from which only the functions specified by the MOCK_FUNCTION("function_name") macros need to be removed.
This eliminates function redefinition errors during compilation and linking.

Would it be possible to implement something like this in Ceedling?

@mvandervoord
Copy link
Member

:) What you are describing here is very close to how scalpel works, which is a new CMock feature. It's currently under development, but likely going to be the release after the soon-to-be-released version.

@parmi93
Copy link
Author

parmi93 commented Oct 9, 2024

This is great news!!
Could you please link me something about this scalpel, I can't find anything on Google about it.

@mvandervoord
Copy link
Member

Sadly at the moment it exists only as a collection of files on my personal repo. It's staged to get added to the next release when some things are worked out.

@parmi93
Copy link
Author

parmi93 commented Oct 9, 2024

ok thanks for the info.
As far as you know, are there any unit test frameworks out there that support this feature?
looking around it seems like no one supports such a thing

@mvandervoord
Copy link
Member

Not that I am aware of. As far as I know, we'd be the first... and that makes me excited to work on it. :)

@parmi93
Copy link
Author

parmi93 commented Oct 10, 2024

In my opinion this will be one of the features of CMock/Ceedling that will make it stand out from the crowd.

@mkarlesky
Copy link
Member

Related issues to collocate in a single issue: #631 #604

@radoslav06
Copy link

I like the idea very much!
It will be also useful to have a possibility to mock all the static functions at once. Something like:

MOCK_STATIC_FUNCTIONS();

@jmrubillon
Copy link

For legacy codebases this feature will be essential as re-organising an entire codebase to introduce a testing framework is unlikely to fly due to the risk of having to revalidate the entirety of the software.
I'm in the middle of doing just that and failing miserably to get a test running on a "module" that has a lot of static functions :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants