diff options
| author | Leitwolf | 2026-11-01 21:19:33 +0300 |
|---|---|---|
| committer | GitHub | 2026-11-01 21:19:33 +0300 |
| commit | 5cbd481125b881a4c2eed74050be9899eb3f23a7 (patch) | |
| tree | 496d26ff659e92cdf4e6fe27a4aa47bf52818052 /docs | |
| parent | 26a1588ae3ae183ae5c900caf3c294bdcb3f8855 (diff) | |
| parent | 8603d31fd8c1a72e30cb444f278d7a0592383ba7 (diff) | |
| download | catch3-5cbd481125b881a4c2eed74050be9899eb3f23a7.tar.gz | |
Merge pull request #30 from Progmasoft/feature/mocking
Add mocks that report through the assertion path
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/Readme.md | 1 | ||||
| -rw-r--r-- | docs/progmasoft-mocking.md | 142 | ||||
| -rw-r--r-- | docs/progmasoft-roadmap.md | 11 | ||||
| -rw-r--r-- | docs/why-catch3.md | 3 |
4 files changed, 151 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..f79a537e --- /dev/null +++ b/docs/progmasoft-mocking.md @@ -0,0 +1,142 @@ +<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 *`, and integers by value whatever their types, so 256 never accepts an `unsigned 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, and Catch's vector matchers, such as +`Equals(vector)`, refer to their vector. Neither compiles here; put them in a +`Where`. A matcher of your own has to hold what it compares with. + +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 `noexcept` function cannot leave the +test case that way, so such a call ends the process. + +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 |