Introduction to CppUTest Mocking
v0.5
CppUTest has support for building mocks. This document described the
mocking support as was released in CppUTest 2.2. A couple of design
goals for the mocking support were:
- Same design goals as CppuTest -- limited C++ set to make it well
suitable for embedded soft.
- No code generation
- No or very few magic hiding macros
- Very simple to use
- The developer stays in control
The main idea is to make manual mocking easier, rather than to make
automated mocks. If manual mocking is easier, then it could also be
automated in the future, but that isn't a goal by itself.
Simple scenario
About the simplest scenario is to check that one particular function
call has happened. The below code is an example of this:
#include "CppUTest/TestHarness.h"
#include "CppUTestExt/MockSupport.h"
TEST_GROUP(MockDocumentation)
{
};
void productionCode()
{
mock().actualCall("productionCode");
}
TEST(MockDocumentation, SimpleScenario)
{
mock().expectOneCall("productionCode");
productionCode();
mock().checkExpectations();
}
The only additional include for mocking is CppUTestExt/MockSupport.h
which is the main include for anything CppUTest Mocking. The
declaration of the TEST_GROUP is the same as always, there is no
difference.
The TEST(MockDocumentation, SimpleScenario) contains the recording of
the expectations as:
mock().expectOneCall("productionCode");
The call to mock() return the global MockSupport (more about that
later) on which we can record our expectations. In this example, we'll
call expectOneCall("productionCode") which... records an expectation
for *one* call to a function called productionCode.
The productionCode call is to show a call to whatever your real code
is. The test ends with checking whether the expectations have been met.
This is done with the:
mock().checkExpectations();
This call is needed when *not* using the MockSupportPlugin, otherwise
this is done automatically for every single test.
The actual mocked function call looks like:
void productionCode()
{
mock().actualCall("productionCode");
}
where we use MockSupport by calling mock() and then record the actual
call to the productionCode function.
This scenarion is quite common when using linker stubbing of e.g. a 3rd
partly library.
If the call to productionCode wouldn't happen, then the test
would fail with the following error message:
ApplicationLib/MockDocumentationTest.cpp:41: error: Failure in TEST(MockDocumentation, SimpleScenario)
Mock Failure: Expected call did not happen.
EXPECTED calls that did NOT happen:
productionCode -> no parameters
ACTUAL calls that did happen:
<none>
Simple scenario using
objects
There is no difference between mocking functions and objects. Below
code shows a mock using run-time mocking:
class ClassFromProductionCodeMock : public ClassFromProductionCode
{
public:
virtual void importantFunction()
{
mock().actualCall("importantFunction");
}
};
TEST(MockDocumentation, SimpleScenarioObject)
{
mock().expectOneCall("importantFunction");
ClassFromProductionCode* object = new ClassFromProductionCodeMock; /* create mock instead of real thing */
object->importantFunction();
mock().checkExpectations();
delete object;
}
The code is self-explanatory. The real object is replaced by a
hand-made mock object. The call to the mock then records the actual
call via the MockSupport.
When using objects, we can also check whether the call was done on the
right object, via this:
mock().expectOneCall("importantFunction").onObject(object);
and the actual call would then be:
mock().actualCall("importantFunction").onObject(this);
If the call to a wrong object happens, it would give the following
error message:
MockFailure: Function called on a unexpected object: importantFunction
Actual object for call has address: <0x1001003e8>
EXPECTED calls that DID NOT happen related to function: importantFunction
(object address: 0x1001003e0)::importantFunction -> no parameters
ACTUAL calls that DID happen related to function: importantFunction
<none>
Using parameters
Of course, just checked whether a function is called is not
particularly useful when we cannot check the parameters. Recording
parameters on a function is done like this:
mock().expectOneCall("function").onObject(object).withParameter("p1", 2).withParameter("p2", "hah");
And the actual call is like:
mock().actualCall("function").onObject(this).withParameter("p1", p1).withParameter("p2", p2);
If a parameter isn't passed, it will give the following error:
Mock Failure: Expected parameter for function "function" did not happen.
EXPECTED calls that DID NOT happen related to function: function
(object address: 0x1)::function -> int p1: <2>, char* p2: <hah>
ACTUAL calls that DID happen related to function: function
<none>
MISSING parameters that didn't happen:
int p1, char* p2
Objects as parameters
withParameters can only use int, double, const char* or void*. However,
parameters are often objects of other types and not of the basic types.
How to handle objects as paramaters? Below is an example:
mock().expectOneCall("function").withParameterOfType("myType", "parameterName", object);
When using withParameterOfType, the mocking framework needs to know how
to compare the type and therefore a Comparator has to be installed
before using parameters of this type. This is done using
installComparator, as below:
MyTypeComparator comparator;
mock().installComparator("myType", comparator);
MyTypeComparator is a customer comparator, which implements the
MockNamedValueComparator interface. For example:
class MyTypeComparator : public MockNamedValueComparator
{
public:
virtual bool isEqual(void* object1, void* object2)
{
return object1 == object2;
}
virtual SimpleString valueToString(void* object)
{
return StringFrom(object);
}
};
The isEqual is called to compare the two parameters. The valueToString
is called when an error message is printed and it needs to print the
actual and expected values. If you want to use normal C functions, you
can use the MockFunctionComparator which accepts pointers to functions
in the constructor.
To remove the comparators, all you needs to do is call
removeAllComparators, like:
mock().removeAllComparators();
Comparators sometimes lead to surprised, so a couple of warnings on its
usage:
Warning 1: Pay attention to the scope of your comparator variable!
Comparators are *not* copied, instead it uses the
exact instance as passed to the installComparator function. So make
sure it is still in-scope when the framework tries to use it! For
example, if you installComparator inside the TEST, but do the
checkExpectations in the teardown, then it is likely to cause a crash
since the comparator has been destroyed.
Warning 2: Pay extra attention to scope when using the MockPlugin
When using the MockPlugin (recommended), then its
best to install the comparators via the MockPlugin or put them in
global space. The checkExpectations will be called *after* teardown and
if your comparator was destroyed in the teardown then this will cause a
crash.
Return values
Sometimes it is needed to let a mock function return a value which can
then be used in production code. This can be done like:
mock().expectOneCall("function").andReturnValue(10);
And it can be used in the actual call like:
int value = mock().actualCall("function").returnValue().getIntValue();
or separate from the actualCall (below it!) like:
value = mock().returnValue().getIntValue();
The return value options are used to transfer data between the test and
the mock object, they themselves do not cause the tests to fail.
Passing other data
Sometimes a test wants to pass more data to the mock object to, for
example, vary only a couple of parameters in a calculation. This can be
done like this:
ClassFromProductionCode object;
mock().setData("importantValue", 10);
mock().setDataObject("importantObject", "ClassFromProductionCode", &object);
And it can be used in the mock object like:
ClassFromProductionCode * pobject;
int value = mock().getData("importantValue").getIntValue();
pobject = (ClassFromProductionCode*) mock().getData("importantObject").getObjectPointer();
Like return values. Setting data will not ever make a test fail but it
provides support in building mock objects.
Other MockSupport - ignoring, enabling, clearing, crashing
MockSupport offers a couple of other useful functions, which will be
covered in this section.
Frequently, you only want to check a couple of calls in your test and
ignore all the other calls. If you add expectOneCall for each of these
calls, you're tests might become too large (though, it might be a smell
that your test is indeed too large). One way to prevent this is the
ignoreOtherCalls, like:
mock().expectOneCall("foo");
mock().ignoreOtherCalls();
This will check that one call of foo happens (and only one call!), but
all other calls will be ignored (such as "bar").
Sometimes, you don't want to just ignore calls, but instead disable the
whole mocking framework for a while (too do *something*). This happens
sometimes in initialization where you might want to do *something*
without the mocking framework checking calls. You can do this by
enabling/disabling such as:
mock().disable();
doSomethingThatWouldOtherwiseBlowUpTheMockingFramework();
mock().enable();
If you want to clear all the expectations, settings, and comparators,
call clear:
mock().clear();
Clear won't do checkExpectations, but just erase everything and start
over. Usually clear() is called after a checkExpectations.
Sometimes, a mock actual call happens, but you cannot figure out from
where it is called. If you only had a call stack, then it you could
track it. Well, unfortunately, the mocking framework doesn't print
stack traces, but it can crash! If you call the crashOnFailure on the
MockSupport, then it will crash so that you can use the debugger to get
a stack trace. like:
mock().crashOnFailure();
When using gdb, get a stack trace using:
gdb examples/CppUTestExamples_tests
r
bt
(r is run, it will run until crashes. bt is back trace which will
produce a stack)
MockSupport Scope
MockSupport can be used hierarchically using MockSupport scope. This
sounds really complex, but in reality it is very simple. When getting a
mock support using the mock function, you can pass a namespace or scope
and record the expectations (or do other things) inside this scope. For
example:
mock("xmlparser").expectOneCall("open");
The actual call then has to look like this:
mock("xmlparser").actualCall("open");
A call on another namespace won't work, for example this won't match
the call to xmlparser open:
mock("").actualCall("open");
Keeping calls in namespaces makes it easy to ignore one type of call
and focus on another, for example:
mock("xmlparser").expectOneCall("open");
mock("filesystem").ignoreOtherCalls();
Less work with MockPlugin
CppUTest plugins can be installed in the main and 'extent' the unit
test framework. It is a place where you can put work that needs to be
done in all unit tests. There is a MockPlugin to make the work with
mocks easier. It does the following work:
- checkExpectations at the end of every test (on global scope,
which goes recursive over all scopes)
- clear all expectations at the end of every test
- install all comparators that were configured in the plugin at the
beginning of every test
- remove all comparators at the end of every test
Installing the MockPlugin means you'll have to add to main something
like:
MyDummyComparator dummyComparator;
MockSupportPlugin mockPlugin;
mockPlugin.installComparator("MyDummyType", dummyComparator);
TestRegistry::getCurrentRegistry()->installPlugin(&mockPlugin);
This code creates a comparator for MyDummy and installs it at the
plugin. This means the comparator is available for all test cases. It
creates the plugin and installs it at the current test registry. After
installing the plugin, you don't have to worry too much anymore about
calling checkExpectations or cleaning your MockSupport.
Working with the C interface
Sometimes it is useful to access the mocking framework from a .c file
rather than a .cpp file. For example, perhaps, for some reason, the
stubs are implemented in a .c file rather than a .cpp file. Instead of
changing over all to .cpp, it would be easier if the mocking framework
can be called via C. The C interface is exactly meant for this. The
interface is based on the C++ one, so below is some code and it ought
to be easy to figure out what it does (if you've read all that was
written earlier):
#include "CppUTestExt/MockSupport_c.h"
mock_c()->expectOneCall("foo")->withIntParamaters("integer", 10)->andReturnDoubleValue(1.11);
mock_c()->actualCall("foo")->withIntParamaters("integer", 10)->returnValue().value.doubleValue;
mock_c()->installComparator("type", equalMethod, toStringMethod);
mock_scope_c("scope")->expectOneCall("bar")->withParameterOfType("type", "name", object);
mock_scope_c("scope")->actualCall("bar")->withParameterOfType("type", "name", object);
mock_c()->removeAllComparators();
mock_c()->setIntData("important", 10);
mock_c()->checkExpectations();
mock_c()->clear();
The C interface uses a similar builder structure as the C++ interface.
It is far less common in C, but it works the same.
Feedback
Any feedback on this mocking framework is more than welcome, please
mail me as basv AT odd-e.com.