Mocking¶
Include mu/tiny/mock.hpp for the C++ mock API.
Accessing the Mock Object¶
The central class is mu::tiny::mock::Support, accessed via the
mu::tiny::mock::mock() free function:
mu::tiny::mock::mock() // global mock scope
mu::tiny::mock::mock("network") // named scope — keeps expectations separate
Named scopes are independent: mock("db") and mock("net") have
separate expectation lists.
Basic Workflow¶
Every test that uses mocks should follow this pattern:
TEST(MyGroup, CallsNetworkSend)
{
// 1. Set expectations
mock("net").expect_one_call("send")
.with_parameter("data", buffer)
.and_return_value(42);
// 2. Exercise the code under test
int result = subject.send_data(buffer);
// 3. Verify
CHECK_EQUAL(42, result);
mock().check_expectations(); // fails if expected calls were not made
mock().clear(); // reset for next test
}
check_expectations() +
clear() in teardown is cleaner than
per-test calls. SupportPlugin does this
automatically — see Plugins.
Expecting Calls¶
Method |
Description |
|---|---|
|
Expect exactly one call to |
|
Expect exactly |
|
Expect that |
Parameter Matching¶
Chain .with_parameter(name, value) on an expected call to constrain
parameter values; use .with_parameter() on the
actual call side with the same overloads. Both accept: bool,
int, unsigned int, long, unsigned long, long long,
unsigned long long, double, const char*, void*,
const void*, void(*)(), and memory buffers
(const unsigned char*, size_t). Fixed-width types from
<stdint.h> (size_t, uint32_t, int64_t, etc.) work
directly because they are typedefs for these fundamental types.
mock().expect_one_call("write")
.with_parameter("fd", 1)
.with_parameter("buf", data_ptr)
.with_parameter("count", (size_t)8);
For double with a tolerance:
mock().expect_one_call("scale")
.with_parameter("factor", 1.0, 0.001); // value ± tolerance
Custom Types¶
Use with_parameter_of_type() on the
expected call and with_parameter_of_type()
on the actual call side for non-native types. A comparator must be installed first — see
Custom Comparators below.
mock().expect_one_call("process")
.with_parameter_of_type("Packet", "pkt", &expected_packet);
Relaxing Parameter Checks¶
.ignore_other_parameters() on an expected call: any extra parameters
the actual call provides beyond the matched set are ignored.
mock().ignore_other_calls() on the mock support: silently accept any
call that has no matching expectation.
Output Parameters¶
Sometimes the mock needs to write data back to the caller through a pointer parameter.
On the expected side:
mock().expect_one_call("read")
.with_output_parameter_returning("buf", &source_data, sizeof(source_data));
On the actual call side (in your mock implementation):
mock().actual_call("read")
.with_output_parameter("buf", output_buffer);
The framework copies source_data into output_buffer when the
call is matched.
For custom types use
with_output_parameter_of_type_returning() +
with_output_parameter_of_type()
with a registered copier.
Return Values¶
Set the return value on the expected call with
and_return_value(). The overload accepts all basic types plus
void*, const void*, and void(*)().
Retrieve the return value in your mock stub with the template accessor
return_value<T>():
// In the mock stub:
return mock().actual_call("compute")
.with_parameter("x", x)
.return_value<int>();
// Or with a default if no expectation was set:
return mock().actual_call("compute")
.with_parameter("x", x)
.return_value_or_default<int>(0);
return_value<T>() and return_value_or_default<T>() are available
on both ActualCall and
Support. They work with any type
that has a NamedValue::get_value<T>() specialization — all fundamental
types plus const char*, void*, const void*, void(*)(),
and any <stdint.h> typedef (uint32_t, int64_t, etc.).
Object Binding¶
Use .on_object() to scope an expectation to a specific object
instance. Useful when multiple objects of the same type are in play.
Widget w1, w2;
mock().expect_one_call("tick").on_object(&w1);
// only a call on &w1 satisfies this expectation
In the mock stub:
mock().actual_call("tick").on_object(this);
Call Ordering¶
By default calls can occur in any order. To enforce order:
mock().strict_order();
mock().expect_one_call("first") .with_call_order(1);
mock().expect_one_call("second").with_call_order(2);
mock().expect_one_call("third") .with_call_order(3);
For a range of acceptable positions: .with_call_order().
Enable / Disable / Tracing¶
mock().disable(); // all actual_call() calls are silently accepted
mock().enable(); // back to normal checking
mock().tracing(true); // print each actual call as it happens
mock().crash_on_failure(); // crash (useful with a debugger) instead of reporting failure
Data Store¶
The mock object doubles as a key/value store. Pass data between the test and stub code without extra globals:
// In test setup:
mock().set_data("timeout_ms", 100);
// In mock stub:
int timeout = mock().get_data("timeout_ms").get_value<int>();
get_value<T>() works with fixed-width types too:
mock().set_data("calibration", static_cast<int64_t>(42));
auto cal = mock().get_data("calibration").get_value<int64_t>();
set_data() is overloaded for: bool, int, unsigned int,
long, unsigned long, long long, unsigned long long,
const char*, double, void*, const void*, void(*)().
For object types:
set_data_object() /
set_data_const_object().
Custom Comparators¶
Install a NamedValueComparator to make
with_parameter_of_type() work for your type:
Template comparator (simplest)¶
TypedMockComparator<T> requires
operator== and string_from():
TypedMockComparator<Packet> packet_cmp;
mock().install_comparator("Packet", packet_cmp);
// Requires: operator==(const Packet&, const Packet&)
// and: mu::tiny::String string_from(const Packet&)
Function-based comparator¶
FunctionComparator wraps plain function
pointers instead of a subclass:
FunctionComparator packet_cmp(
[](const void* a, const void* b) {
return *static_cast<const Packet*>(a) == *static_cast<const Packet*>(b);
},
[](const void* a) -> mu::tiny::String {
return static_cast<const Packet*>(a)->to_string();
}
);
mock().install_comparator("Packet", packet_cmp);
Subclass comparator¶
Subclass NamedValueComparator directly
for full control:
class PacketComparator : public mu::tiny::mock::NamedValueComparator
{
public:
bool is_equal(const void* a, const void* b) override {
return *static_cast<const Packet*>(a) == *static_cast<const Packet*>(b);
}
mu::tiny::String value_to_string(const void* a) override {
return static_cast<const Packet*>(a)->to_string();
}
};
Remove all installed comparators and copiers with
mock().remove_all_comparators_and_copiers().
Custom Copiers¶
A NamedValueCopier is needed when using
output parameters of custom types:
TypedMockCopier<T> uses operator=:
TypedMockCopier<Packet> packet_copier;
mock().install_copier("Packet", packet_copier);
// Requires: Packet::operator=
FunctionCopier wraps a plain copy function:
FunctionCopier packet_copier([](void* dst, const void* src) {
*static_cast<Packet*>(dst) = *static_cast<const Packet*>(src);
});
mock().install_copier("Packet", packet_copier);
SupportPlugin¶
SupportPlugin automatically calls
check_expectations() and
clear() after every test, and manages
comparator/copier lifetime:
// main.cpp
mu::tiny::mock::SupportPlugin mock_plugin;
mock_plugin.install_comparator("Packet", packet_cmp);
reg->install_plugin(&mock_plugin);
With the plugin installed, tests no longer need explicit
check_expectations()/clear() calls. See Plugins.
Examples¶
Core mock workflow —
expect_one_call(),
expect_n_calls(),
with_parameter(),
and_return_value(),
ignore_other_parameters(),
check_expectations(),
clear():
#include "mu/tiny/mock.hpp"
#include "mu/tiny/test.hpp"
using mu::tiny::mock::mock;
namespace {
/* Stubbed out product code using linker, function pointer, or overriding */
int foo(const char* param_string, int param_int)
{
/* Tell mutiny Mocking what we mock. Also return recorded value */
return mock()
.actual_call("Foo")
.with_parameter("param_string", param_string)
.with_parameter("param_int", param_int)
.return_value()
.get_value<int>();
}
void bar(double param_double, const char* param_string)
{
mock()
.actual_call("Bar")
.with_parameter("param_double", param_double)
.with_parameter("param_string", param_string);
}
/* Production code calls to the methods we stubbed */
int production_code_foo_calls()
{
int return_value;
return_value = foo("value_string", 10);
(void)return_value;
return_value = foo("value_string", 10);
return return_value;
}
void production_code_bar_calls()
{
bar(1.5, "more");
bar(1.5, "more");
}
} // namespace
TEST_GROUP(MockCheatSheet)
{
void teardown() override
{
/* Check expectations. Alternatively use SupportPlugin */
mock().check_expectations();
mock().clear();
}
};
TEST(MockCheatSheet, foo)
{
/* Record 2 calls to Foo. Return different values on each call */
mock()
.expect_one_call("Foo")
.with_parameter("param_string", "value_string")
.with_parameter("param_int", 10)
.and_return_value(30);
mock().expect_one_call("Foo").ignore_other_parameters().and_return_value(50);
/* Call production code */
production_code_foo_calls();
}
TEST(MockCheatSheet, bar)
{
/* Expect 2 calls on Bar. Check only one parameter */
mock()
.expect_n_calls(2, "Bar")
.with_parameter("param_double", 1.5)
.ignore_other_parameters();
/* And the production code call */
production_code_bar_calls();
}
Further examples:
MockDocumentation.test.cpp —
on_object(),set_data(), custom comparator, scoped mock,crash_on_failure(),disable()/enable(),ignore_other_calls()EventDispatcher.test.cpp — real-world example: virtual mock class, custom comparator in
setup(),with_parameter_of_type()