mimic++ v5
|
Dominic Koepke
Mail: DNKpp2011@gmail.com
mimic++
is a c++20 mocking framework, which aims to offer a natural end expressive syntax, without constantly resorting to macros. To be honest, macros cannot be completely avoided, but they can at least be reduced to a very minimum.
The one thing, that this framework does different than all other (or at least all I am aware of) mocking framework is, that mock objects explicitly are function objects, thus directly callable. It's simple and straight forward.
As I'm mainly working on template or functional-style code, I wanted something simpler than always creating explicit mock types for the simplest of use cases. So, mimicpp::Mock
objects can directly be used as functional objects, but they can also be used as member objects and thus serve as actual member functions.
If you are curious, have a look at the documentation, investigate the examples folder or play around online at godbolt.org.
The framework is designed with two core concepts in mind: Mocks and Expectations. Mocks can be used to define behaviour on a per test-case basis, without the necessity of creating dozens of types. The go-to example is, if you have a custom type, which somehow makes a connection to a concrete database, you do not want to setup an actual database connection during your test runs. You then simply install a database mock, which then yields the exact replies as it were defined for that particular case: the so called "Expectations".
So, Mocks and Expectations are going together hand in hand.
As already said, mimicpp::Mock
s are already function objects.
mimicpp::Mock
s can also serve as member functions. Sure, there are some limitations, but for the most cases it works well.
mimic++
also provides helpers for interface mocking.
Sometimes, an interface method may also have several overloads. mimic++
directly supports overriding overload-sets.
mimicpp::Watched
helper can report destruction and relocations of object-instances.
mimic++
is designed to work on any c++20 conforming compiler - independent of the underlying platform or architecture. This is achieved by always staying within the language bounds and is continuously checked by the extensive ci/cd workflow, which keeps track of many different configurations. In fact mimic++
is known to work on Windows, Ubuntu and MacOs with x86_x64
and x86_x86
architectures. For a more complete overview head over to the Testing section.
This framework is highly inspired by the well known trompeloeil, which I have used myself for several years now. It's definitly not bad, but sometimes feels a little bit dated and some macros do not play very well with formatting tools and the like. If you need a pre-c++20 mocking-framework, you should definitly give it a try.
Fun fact: mimic++
uses trompeloeil
for it's own test suite :D
A framework should be a useful tool that can be used in a variety of ways. However, it should not be a foundation that limits the house to be built on it. For this reason mimic++
offers various ways for customization: E.g. users may create their own expectation policies and integrate them seamlessly, without changing any line of the mimic++
code-base.
mimic++
can not provide stringification for any type out there, but it's often very useful to see a proper textual reprensentation of an object, when a test fails. mimic++
will use std::format
for formattable
types, but sometimes that is not, what we want, as users for example want to have an alternative stringification just for testing purposes. Users can therefore add their specializations of the mimicpp::custom::Printer
type and thus tell mimic++
how a given type shall be printed.
Custom specializations will always be prefered over any pre-existing printing methods, thus users may even override the stringification of the internal report types.
Matchers are used to check, whether arguements satisfy some requirements. There are already many existing matchers available, but users often have special needs. mimic++
provides a very generic mimicpp::PredicateMatcher
, which is often already sufficient. But, if one needs full control, just start with a fresh type (without any inheritance) and start building your own. They just have to match the mimicpp::matcher_for
concept. For more information, head to the documentation.
There are multiple types of policie, depending on the task they shall fulfill. The expectation policy has full control, whether a match can be made or shall be rejected, while the finalize policy determines, what a mock shall do, when it actually matched (like returning a value or throwing an exception). They can implement arbitrary logic, so feel free to experiment. There is no base type requirement, they simply have to satisfy either the mimicpp::expectation_policy_for
, mimicpp::control_policy
or mimicpp::finalize_policy_for
.
If you are working with a huge framework, chances are good, that this framework utilizes a custom string- or even char-type (like QChar
and QString
from Qt). They may look different, but in fact they are just strings, so it would be nice to make them fully compatible with the existing string-matchers. mimic++
supports that, users just have to provide some trait-specializations. For more infos, have a look into the string section of the doxygen documentation.
Call-Conventions are a rather controversial topic, as the c++-language definition doesn't know or say anything about them by itself. On the other hand-side, the microsoft COM
framework for example makes use of the __stdcall
call-convention; so it's safe to say, that at least some compilers have support for these kind of specifications; and of course users need the possibility to make use of these features. As they are by no means portable, mimic++
does not define anything by itself, but does instead provide an easy ( macro) tool to users, which they can use to make the framework compatible with any call-convention they need: MIMICPP_REGISTER_CALL_CONVENTION
The documenation is generated via doxygen
. Users can do this locally by enabling both, the MIMICPP_CONFIGURE_DOXYGEN
and MIMICPP_ENABLE_GENERATE_DOCS
, cmake options and building the target mimicpp-generate-docs
manually.
The documentation for the main
branch is always available on the github-pages; for the development
branch it is also available on the dev-gh-pages
branch, but unfortunatly not directly viewable on the browser. Every release has the generated documentation attached.
mimic++
is a head-only library. Users can easily enjoy all features by simply including the mimic++/mimic++.hpp
header. Of course one can be more granular and include just what's necessary. The choice is yours.
This framework is header-only and completely powered by cmake, thus the integration into a cmake project is straight-forward.
Users can either pick a commit in the main
branch or a version tag and utilize the cmake FetchContent
module:
As an alternative, I recommend the usage of CPM, which is a featureful wrapper around the FetchContent
functionality:
As an alternative each release has a header file named mimic++-amalgamated.hpp
attached, which contains all definitions (except for the specific test-framework adapters) and can be simply dropped into any c++20 project (or used on godbolt.org. After that, users may also just pick the appropriate adapter header for their desired test-framework and put that into their project aswell.
Mocking frameworks usually do not exist for their own, as they are in fact just an advanced technice for creating tests. Instead, they should work together with any existing test framework out there. mimic++
provides the IReporter
interface, which in fact serves as a bridge from mimic++
into the utilized test framework. mimic++
provides some concrete reporter implementations for well known test frameworks, but users may create custom adapters for any test framework or simply use the default reporter. For more details have a look into the reporting
section in the documentation.
Official adapters exist for the following frameworks:
mimic++
utilizes a strict testing policy, thus each official feature is well tested. The effect of those test-cases are always tracked by the extensive ci, which checks the compilation success, test cases outcomes and coverage on dozens of different os, compiler and build configurations. By default, all configurations assume a x86_x64
-architecture. In addition, a few configurations have been added specifically for the x86_x86
-architecture.
For the test builds the flags -Wall -Wextra -Wpedantic -Werror
(on MSVC /W4 /WX
) are set. This is done to make sure, that mimic++
won't flood your build output with endless warnings (or even worse: break your builds), if you enable these flags in your own projects.
The coverage is generated via gcov
and evaluated by codacy, codecov and coveralls.
Nevertheless, even with very high effort, the 100% code-coverage is without reach. I do my best to cover each branch, but even now the coverage percentage varies between the individual tools. The goal is to be close to 100% as possible.
On the other hand-side, there is a whole range of code which doesn't even get investigated by these tools: templates (and macros). mimic++
has a lot of templating code at the very heart, which is at least of equal effort to get right (and tested). So, treat the coverage percentage with a grain of salt.
The listed configurations are explicitly tested, but other do probably work, too. As new compilers become available, they will be added to the workflow, but older compilers will probably never be supported.
Symbol | Description |
---|---|
x | works |
* | works with caveats |
- | does not work |
? | not tested |
Windows
OS | Compiler | c++-20 | c++-23 | std-format | fmt |
---|---|---|---|---|---|
Windows 2022 | msvc | x | x | x | x |
Windows 2022 | clangCl | x | x | x | x |
Linux
Compiler | libstdc++ | libc++ | c++-20 | c++-23 | std-format | fmt |
---|---|---|---|---|---|---|
clang-16 | x | x | x | x | - | x |
clang-17 | x | x | x | x | x | x |
clang-18 | x | * | x | x | x | x |
gcc-12 | x | ? | x | x | - | x |
gcc-13 | x | ? | x | x | x | x |
gcc-14 | x | ? | x | x | x | x |
macOS
Compiler | libstdc++ | libc++ | c++-20 | c++-23 | std-format | fmt |
---|---|---|---|---|---|---|
AppleClang-16.0.6 | ? | x | x | x | - | x |
AppleClang-17.0.6 | ? | x | x | x | x | x |
AppleClang-18.1.6 | ? | x | x | x | x | x |
BSL-1.0 (free, open source)
Date: 25.09.2024
This combination introduced a regression regarding the std::invocable
concept and a default parameter of type std::source_location
. On this version, all invocable checks will fail, but the std::is_invocable
trait still works as expected. Unfortunatly this can not solved easily by this lib, sorry for that.
Clang-17 and Clang-19 do not suffer from this issue. For more information have a look here.