mimic++ v1
|
Sequences enable deterministic ordering between multiple expectations. More...
Classes | |
class | mimicpp::Sequence |
The user level sequence object. More... | |
Enumerations | |
enum class | mimicpp::SequenceId : std::size_t |
Strong type for internally used sequence ids. More... | |
Functions | |
auto | mimicpp::expect::in_sequence (Sequence &sequence, const std::size_t times=1u) |
Attaches the expectation onto a sequence. | |
template<std::size_t size> | |
auto | mimicpp::expect::in_sequences (const std::reference_wrapper< Sequence >(&sequences)[size], const std::size_t times=1u) |
Attaches the expectation onto the listed sequences. | |
Sequences enable deterministic ordering between multiple expectations.
Their aim is to provide a convenient way for users, to circumvent the rather loosely ordering of expectations, which is by design. By default, if two or more expectations would match a call, the last created one is used. If multiple expectations are built one after another, that may be rather unintuitive design, but when it comes to scoping, it's usually preferable to match the expectations from within the current scope, even if there exist another similar expectation from the outside scope.
As I have no idea how to use one strategy for one case and the other for the other case, I decided to take the same route as trompeloeil
.
Either way, this is sometimes not enough, when we want to enforce deterministic ordering between two or more expectations. That's where sequences come into play. The expectation, which gets attached first must match before all subsequent expectations. If that one is fulfilled, the next one in the row must be matched; and so on.
Sequences can also enforce orders on expectations, which refer to different mocks.
Sequenced and non-sequenced expectations may be arbitrarily mixed; even if this can be very difficult to trace, by simply reviewing the code.
It's totally fine to attach expectations to sequences, which are already queried for matches. Sequences do not have to be setup in one go.
Sequences are not thread-safe and are never intended to be. If one attempts to enforce a strong ordering between multiple threads without any explicit synchronisation, that attempt is doomed to fail.
Sequence policies are treated as times_policy
and therefore can not be mixed with e.g. Times
. At a first glance this seems rather unintuitive, as a sequence does not actually mind how often an expectation is matched, but rather if one expectation has been matched before another. This being true, there is still a huge intersection between those cases, because a sequence still must know how often an expectation is expected to match. This would lead to higher coupling between two rather unrelated domains, but isn't the actual deal-breaker. The actual reason is, that Times
is rather permissive designed and thus allow for a wide range of valid configurations. Especially the cases, where a range of possible calls is configured (like at_least
), makes it very hard for a sequence to reliably determine, whether an expectation shall match or not. When users define expectations not precise enough, this quickly leads to ambiguities between multiple expectations which may and will result in surprising outcomes.
|
strong |
Strong type for internally used sequence ids.
|
inlinenodiscard |
Attaches the expectation onto a sequence.
sequence | The sequence to be attached to. |
times | The expected times. namespace matches = mimicpp::matches;
namespace expect = mimicpp::expect;
mimicpp::Mock<void(int)> mock{};
mimicpp::Sequence sequence{};
SCOPED_EXP mock.expect_call(matches::ne(0))
| expect::in_sequence(sequence);
SCOPED_EXP mock.expect_call(matches::le(42))
| expect::in_sequence(sequence);
// a call with arg != 0 is expected before a call with arg <= 42
mock(42); // matches the first expectation
mock(0); // matches the second expectation
// note: when we change the call order, the sequence would be violated
using mimicpp::matches::_;
namespace expect = mimicpp::expect;
mimicpp::Mock<void(int)> mock{};
SCOPED_EXP mock.expect_call(42); // (1)
SCOPED_EXP mock.expect_call(1337); // (2)
mimicpp::Sequence sequence{};
SCOPED_EXP mock.expect_call(1337) // (3)
| expect::in_sequence(sequence);
SCOPED_EXP mock.expect_call(1337); // (4)
SCOPED_EXP mock.expect_call(42) // (5)
| expect::in_sequence(sequence);
mock(42); // matches (1), because (5) is the second element of the sequence
mock(1337); // matches (4), because it's the "youngest" available alternative
mock(1337); // matches (3), because it's "younger" than (2). So, the first element of the sequence got matched.
mock(42); // matches (5). The sequence is now fulfilled.
mock(1337); // finally matches (2)
|
|
nodiscard |
Attaches the expectation onto the listed sequences.
sequences | The sequences to be attached to. |
times | The expected times. using mimicpp::matches::_;
namespace expect = mimicpp::expect;
mimicpp::Mock<void()> mock{};
mimicpp::Sequence sequence1{};
mimicpp::Sequence sequence2{};
SCOPED_EXP mock.expect_call() // (1)
| expect::in_sequence(sequence1);
SCOPED_EXP mock.expect_call() // (2)
| expect::in_sequences({sequence1, sequence2});
SCOPED_EXP mock.expect_call() // (3)
| expect::in_sequence(sequence2);
mock(); // (1) is used here, because (3) is second in sequence2 and (2) is second in sequence1
mock(); // (2) is used here, because it's the first in both sequences now
mock(); // now (3) is used
|