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

mock().expect_one_call("func")

Expect exactly one call to "func"

mock().expect_n_calls(n, "func")

Expect exactly n calls

mock().expect_no_call("func")

Expect that "func" is never called

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: