diff options
| author | Leitwolf11 | 2026-11-01 19:04:52 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-11-01 19:04:52 +0300 |
| commit | c51d15273e87267215e5877edc268e7db354187b (patch) | |
| tree | bc314b97cbb793ea136ceabecc3d565240ae7cf2 /docs | |
| parent | 26a1588ae3ae183ae5c900caf3c294bdcb3f8855 (diff) | |
| download | catch3-c51d15273e87267215e5877edc268e7db354187b.tar.gz | |
Add mocks that report through the assertion path
Step 3 of the roadmap. MockFunction checks the calls of one function
against its expectations; CATCH3_MOCK_METHOD declares one for a virtual
member, and CATCH3_MOCK_FUNCTION and ScopedSeam serve free functions
behind a seam.
Expectations take values, Catch matchers, Any or Where; count calls with
Times, Between, AtLeast, AtMost, AnyTimes and Never; answer with Returns,
Does or Throws; and order calls across mocks with Sequence. A failure
goes through AssertionHandler at the line of the expectation, so every
report of the runner shows it. Met expectations count as passed
assertions. A test case that is already being left does not get unmet
expectations on top of its own failure.
Composed matchers refer to temporaries, so they are rejected at compile
time rather than left dangling. A call that fails returns a
value-initialized result, or leaves the test case when no such result
exists.
The failure paths are tested through the runner fixture, reading the
messages from its JSON report.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/Readme.md | 1 | ||||
| -rw-r--r-- | docs/progmasoft-mocking.md | 140 | ||||
| -rw-r--r-- | docs/progmasoft-roadmap.md | 11 | ||||
| -rw-r--r-- | docs/why-catch3.md | 3 |
4 files changed, 149 insertions, 6 deletions
diff --git a/docs/Readme.md b/docs/Readme.md index 3da1a8ca..15fe412d 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -38,6 +38,7 @@ Catch2 release. **Running:** * [The Catch3 runner](progmasoft-runner.md#top) +* [Mocking](progmasoft-mocking.md#top) * [Command line reference](command-line.md#top) * [Running specific section/generator](filtering-execution-path.md#top) diff --git a/docs/progmasoft-mocking.md b/docs/progmasoft-mocking.md new file mode 100644 index 00000000..98c372ec --- /dev/null +++ b/docs/progmasoft-mocking.md @@ -0,0 +1,140 @@ +<a id="top"></a> + +# Mocking + +`<Progmasoft/Catch3/Mock.hpp>` replaces a dependency of the code under test +with a mock that checks how it is called. Mocks report through Catch's +assertion path: an unexpected call, a call out of order and an unmet +expectation fail the test case that owns the mock, at the line that declared +the expectation, in every report the runner writes. + +```cpp +#include <Progmasoft/Catch3/Mock.hpp> +#include <Progmasoft/Catch3/TestMacros.hpp> + +class Store +{ +public: + virtual ~Store() = default; + virtual int Get(const std::string &key) const = 0; + virtual void Put(const std::string &key, int value) = 0; +}; + +class MockStore final : public Store +{ +public: + CATCH3_MOCK_METHOD(int, Get, (const std::string &), const override); + CATCH3_MOCK_METHOD(void, Put, (const std::string &, int), override); +}; + +TEST_CASE("A score goes up by one") +{ + MockStore store; + CATCH3_EXPECT_CALL(store.GetMock)("ada").Returns(41); + CATCH3_EXPECT_CALL(store.PutMock)("ada", 42); + + AddPoint(store, "ada"); +} +``` + +A mock verifies itself in its destructor, so nothing has to be called at the +end of the test case. When the test case is already being left by a failure or +a skip, the expectations it did not reach are not reported on top of it. +`Verify()` checks the expectations earlier and forgets them, for a test case +that goes on with new ones. + +## Declaring mocks + +`CATCH3_MOCK_METHOD(result, name, (types), specifiers)` declares the member +function `name` and the mock `nameMock` beside it. The specifiers are whatever +follows the parameters: `const`, `noexcept`, `override`. Up to 8 parameters are +supported, and a type with a comma in it, such as `std::map<std::string, int>`, +needs an alias. + +Overloads need mocks with names of their own: + +```cpp +CATCH3_MOCK_OVERLOAD(void, Put, PutOne, (const std::string &, int), override); +CATCH3_MOCK_OVERLOAD(void, Put, PutAll, (const Scores &), override); +``` + +`CATCH3_MOCK_FUNCTION(name, signature)` declares a `MockFunction` on its own. + +## Setting expectations + +`CATCH3_EXPECT_CALL(mock)(arguments...)` expects one call. Without arguments it +accepts any arguments; otherwise each parameter takes one of these: + +| Written as | Accepts | +| --- | --- | +| a value | what compares equal to it; strings compare by value, also as `const char *` | +| a Catch matcher, such as `StartsWith("a")` | what the matcher accepts | +| `Any` | anything | +| `Where(predicate, "description")` | what the predicate accepts | + +A composition such as `StartsWith("a") && EndsWith("z")` refers to temporaries +that are gone when the mock is called, so it does not compile here; put it in a +`Where`. + +The result of `CATCH3_EXPECT_CALL` takes these, in any order: + +| Member | Meaning | +| --- | --- | +| `Times(n)`, `Between(a, b)`, `AtLeast(n)`, `AtMost(n)`, `AnyTimes()`, `Never()` | how many calls it wants; one by default | +| `Returns(value)` | the result of each call; a type that cannot be copied moves out on the first one, and a reference result refers to `value` | +| `Does(function)` | runs `function` with the arguments, and returns what it returns; it keeps its state between calls | +| `Throws(exception)` | throws `exception` from each call | +| `InSequence(sequence)` | see below | + +A call goes to the first expectation, in the order they were declared, that +accepts its arguments and can take another call. A call none of them accepts +fails the test case and returns a value-initialized result. So does a call they +accept when all of them are used up. When the result cannot be made that way, a +reference for example, the test case ends there; the same happens to an +expectation with no `Returns` or `Does`. + +A mock with no expectation for a call treats it as a failure. An expectation +with `AnyTimes()` is how to allow calls that do not matter to the test. + +## Sequences + +Expectations in a `Sequence` have to be met in the order they were declared in, +across mocks: + +```cpp +Sequence order; +CATCH3_EXPECT_CALL(file.OpenMock)().InSequence(order); +CATCH3_EXPECT_CALL(file.WriteMock)(Any).AtLeast(1).InSequence(order); +CATCH3_EXPECT_CALL(log.FlushMock)().InSequence(order); +``` + +A step that is passed takes no more calls. A step reached before the ones ahead +of it have their calls fails the test case and names them. + +## Free functions + +Code that reaches a free function through a seam, a `std::function` or a +function pointer it calls through, can be pointed at a mock. `ScopedSeam` puts +the seam back at the end of its scope: + +```cpp +CATCH3_MOCK_FUNCTION(clock, long()); +CATCH3_EXPECT_CALL(clock)().Returns(50L); + +const ScopedSeam seam(clockSeam, clock.AsFunction()); +CHECK(SecondsSince(20) == 30); +``` + +The mock has to outlive what `AsFunction` returns. A function pointer can only +take a function without state, so it reaches a mock of static lifetime through +one. + +## Threads + +Mocks count and order calls correctly when several threads call them. Reports +go through Catch's assertions, which are not safe across threads unless +`CATCH_CONFIG_EXPERIMENTAL_THREAD_SAFE_ASSERTIONS` is set, so a failure in a +thread other than the test's needs that setting. + +Outside of a test case, such as in the destructor of a global, there is nothing +to fail: failures go to standard error. diff --git a/docs/progmasoft-roadmap.md b/docs/progmasoft-roadmap.md index 5ab04264..81a8956f 100644 --- a/docs/progmasoft-roadmap.md +++ b/docs/progmasoft-roadmap.md @@ -21,7 +21,7 @@ The steps are ordered by what each one builds on. | --- | --- | --- | | 1 | Native property testing: choice-sequence shrinking, combinators, state machines, statistics, targeted search, example database, differential and metamorphic oracles | done; see [Native property testing](progmasoft-properties.md#top) | | 2 | The Catch3 runner | done; see [The Catch3 runner](progmasoft-runner.md#top) | -| 3 | Mocking | planned | +| 3 | Mocking | done; see [Mocking](progmasoft-mocking.md#top) | | 4 | Per-test code coverage | planned | | 5 | Change-impact test selection and test prioritization | planned | | 6 | Mutation testing | planned | @@ -56,10 +56,11 @@ Catch3 runner replaced Catch2's in every test binary: ## 3. Mocking -Mocks that report through Catch2's assertion path: expectations with call -counts, argument matchers built on Catch2's matchers, sequences, and actions, -for virtual interfaces and for free functions behind a seam. Unexpected calls -and unmet expectations fail the test case that owns the mock. +Mocks report through Catch2's assertion path: expectations with call counts, +argument matchers built on Catch2's matchers, sequences, and actions, for +virtual interfaces and for free functions behind a seam. Unexpected calls and +unmet expectations fail the test case that owns the mock. See +[Mocking](progmasoft-mocking.md#top). ## 4. Per-test code coverage diff --git a/docs/why-catch3.md b/docs/why-catch3.md index 120295f0..871b6efc 100644 --- a/docs/why-catch3.md +++ b/docs/why-catch3.md @@ -20,13 +20,14 @@ Catch2's runner with [its own](progmasoft-runner.md#top). | C++ baseline | C++17; the headers work in C++17 and later | The same | | Randomness | Catch2's existing behavior is unchanged | A specified SplitMix64 sequence provides deterministic trial seeds and unbiased bounded integer sampling | | Generated values | No change to existing Catch2 generators | Strategies for numbers, text, collections and structures with `Map`, `Filter` and `FlatMap`; shrinking of recorded choices, so every combination shrinks; replay tokens and an example database; state machines, `Classify`/`Cover` statistics, targeted search, and differential and metamorphic oracles | +| Mocks | None | [`CATCH3_MOCK_METHOD`, `CATCH3_EXPECT_CALL`](progmasoft-mocking.md#top) and `Sequence`, reporting through the assertion path | | Build identity | One library: the `Catch3` CMake package with `Catch3::Catch3` and `Catch3::Catch3WithMain`, `//:catch3` in Bazel, `catch3` in Meson and pkg-config | The same library | | Source terms | Catch2-originated source retains its upstream BSL-1.0 terms | Progmasoft-authored additions state their own SPDX license in each file | Properties take any number of generated values. Shrinking is deterministic and bounded, but local rather than globally minimal. Parallel trials do not exist yet; [the roadmap](progmasoft-roadmap.md#top) -orders them with mocking, per-test coverage, test selection, mutation testing +orders them with per-test coverage, test selection, mutation testing and test generation. ## Compatibility policy |