Tests & Suites

Now that you have the hang of how to test code, lets take a look at how to structure test cases for µnit. First, each test should be a separate function with the following prototype:

MunitResult my_test(const MunitParameter params[], void* user_data_or_fixture);

The name of the test ( my_test in this case) doesn't matter; it's for your internal use only. As for the arguments, we'll get back to them soon.

There are four possible results in µnit which can be returned from a test:

MUNIT_OK The test passed. MUNIT_SKIP The test was skipped for some reason. It will not be counted as a success or failure, and will not cause the suite to fail. MUNIT_FAIL The test failed. If an assertion fails, this is the result. MUNIT_ERROR There was an error in code that you weren't trying to test. For example, maybe a test downloads a file from the internet and parses it; if your test can't connect to the server you may want to use ERROR instead of FAIL.

Each thing you want to test should be a separate function. It can be tempting to just have the test suite call one function and have that function test everything, but it will make your life harder in the long run.

The MunitTest struct

Once you have a test case (or, even better, more than one!) it's time to put them together in a suite. First, you'll want to create an array of MunitTest s:

MunitTest tests[] = { { "/my-test", /* name */ my_test, /* test */ NULL, /* setup */ NULL, /* tear_down */ MUNIT_TEST_OPTION_NONE, /* options */ NULL /* parameters */ }, /* Mark the end of the array with an entry where the test * function is NULL */ { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } };

The name is a human-readable identifier for the test. The convention for µnit is to start each name with a forward slash, but technically it's not required. Actually, as long as there are no NULL characters in the name you can technically do pretty much whatever you want.

The second field, test , is just the function you created earlier.

We'll come back to setup and tear_down in a minute. options is a bitmask of options; currently the only values are:

MUNIT_TEST_OPTION_NONE Use the default options; 0 works here, too, but some compilers might emit a warning. MUNIT_TEST_OPTION_SINGLE_ITERATION µnit includes the ability to run tests multiple times, a feature we'll discuss in a bit more detail soon. For now, however, just know that this can be disabled on a per-test basis with this flag.

Setup and tear-down functions

If a setup function is provided as part of a test, it is called before the test function is invoked, and the return value of the setup function is passed as the user_data_or_fixture parameter to the test function.

Similarly, if a tear down function is provided, it will be called after the test function with the fixture argument set to the return value of the setup function.

This is commonly used to initialize and destroy structures and resources used by the test. For example:

static void* test_setup(const MunitParameter params[], void* user_data) { return strdup("Hello, world!"); } static void test_tear_down(void* fixture) { free(fixture); } static MunitResult test(const MunitParameter params[], void* fixture) { char* str = (char*) fixture; munit_assert_string_equal(str, "Hello, world!"); return MUNIT_OK; }

The MunitSuite struct

Once you have your array of tests, it's time to put them in a test suite:

static const MunitSuite suite = { "/my-tests", /* name */ tests, /* tests */ NULL, /* suites */ 1, /* iterations */ MUNIT_SUITE_OPTION_NONE /* options */ };

Like the test name, the suite name typically begins with a forward slash, though it's not required. When you run the suite, the suite name will be concatenated with the test name to determine the full test name. For example, the name of the test in this suite will be "/my-tests/my-test".

The second field, tests , is the array of tests you created earlier.

The suites field allows you to embed one test suite in another. For our simple example we've set it to "NULL" to indicate there are no sub-suites, but in practice you'll commonly want to use this feature to help organize tests for larger projects where it's convenient to split your unit tests across multiple files.

Another interesting use case for nested suites is projects which include other projects. If both projects use µnit it becomes easy to include the sub-project's unit tests when running the parent's.

After suites is iterations . Generally you'll want a single iteration of each test, but if your tests are fast include randomization, or the possibility of a race condition, you might want to run each test multiple times.

Finally, there is the options field. Currently there are no suite-level options, but this field is provided for future expansion.

Calling munit_main

Once you have your suite ready to go, all that is left is to call munit_main() , which will parse any command line arguments and run the suite:. The prototype looks like:

int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, const char* argv[]);

Most of this is probably pretty self-explanitory; you pass it a pointer to the suite, as well as the command line arguments. We'll (finally) talk about user_data in the next section, but for now you can just pass "NULL".

The return value will be "EXIT_FAILURE" if any tests fail, or "EXIT_SUCCESS" if all tests succeed. This makes the value suitable for returning directly from your main() function.

In the simplest case you'll end up with something like this:

int main (int argc, const char* argv[]) { return munit_suite_main(&suite, NULL, argc, argv); }

User Data and Fixtures

You've probably noticed that we've been basically ignoring some arguments; namely, user_data and parameters . We're going to continue ignoring parameters for now (we'll get to them in the Parameterized Tests section), but it's finally time to talk about user_data .

If there is no setup function in a test, the user_data parameter which you pass to munit_main() is passed to the test function as the user_data_or_fixture parameter.