# RapidCheck: property based testing for C++ with shrinking built in

> RapidCheck is a C++ property based testing framework inspired by QuickCheck, where you state a fact about your code and the library generates random inputs until the fact breaks, then shrinks the failure to the smallest case. It suits C++ teams that want their invariants tested by a framework rather than by hand written examples, and it depends on C++11.

**emil-e/rapidcheck** — QuickCheck clone for C++ with the goal of being simple to use with as little boilerplate as possible.

- Repository: https://github.com/emil-e/rapidcheck
- Stars: 1,112 · Forks: 189
- Language: C++
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/emil-e-rapidcheck

## You state a fact, the framework tries to break it

RapidCheck is a C++ framework for property based testing inspired by QuickCheck and similar frameworks. The idea it applies is that you state facts about your code which, given certain preconditions, should always be true, and the framework then generates random test data to try to find a case for which the property does not hold.

What happens next is the part that distinguishes a good property testing framework. If such a case is found, RapidCheck tries to find the smallest case, for some definition of smallest, for which the property is still false, and then displays it as a counterexample. For an integer input, that means trying to find the smallest integer for which the property fails.

The author is explicit about the motivation. Existing implementations of property based testing were, in his words, either a bit clunky or missing essential features such as test case shrinking. Shrinking is not a nicety, it is the difference between a failure report and a fix. A fuzzer that finds a 4000 element vector with a bad element somewhere in the middle has found a bug, a property test that shrinks that to a two element vector has found the bug and told you where it is.

The language is C++, the licence is BSD-2-Clause, and the last push was on 2026-10-05. The repository is not archived and there are no GitHub releases, so the project is maintained without a tagged version stream.

## The first example is a double reversal, and the failure is instructive

The quick introduction in the documentation tests a reversal function, where double reversal should always give back the original list, using the standard `std::reverse`:

```cpp
#include <rapidcheck.h>

#include <vector>
#include <algorithm>

int main() {
  rc::check("double reversal yields the original value",
            [](const std::vector<int> &l0) {
              auto l1 = l0;
              std::reverse(begin(l1), end(l1));
              std::reverse(begin(l1), end(l1));
              RC_ASSERT(l0 == l1);
            });

  return 0;
}
```

The API is small on purpose. `rc::check` takes an optional string describing the property and a callable implementing it, in this case a lambda. Any parameters to that callable, here `l0`, are randomly generated. The `RC_ASSERT` macro works like any other assert macro, and a false condition means the property has been falsified.

A passing run prints the configuration used, including the random seed, and the number of cases run:

```text
Using configuration: seed=9928307433081493900

- double reversal yields the original value
OK, passed 100 tests
```

The failing run is where the design pays off:

```text
Falsifiable after 12 tests and 10 shrinks

std::tuple<std::vector<int>>:
([1, 0, 0, 0, 0, 0, 0, 0, 0, 0])

main.cpp:17:
RC_ASSERT(l0 == l1)
```

The report says the property was falsified after 12 tests, and that the case was shrunk 10 times to reach the counterexample. The counterexample contains each input value used for the failing case along with its type, and since RapidCheck views property arguments as tuples, the type is shown as `std::tuple<std::vector<int>>`. The `Expands to` section then shows the assertion side by side with the expected value, so you can see the difference rather than infer it.

The documentation even walks through the diagnosis. The fact that there are exactly 10 items should give a clue, and the bug is that the implementation sets the first element to 0 when the size is at least 10. That also explains the leading 0, since the problem does not manifest when all elements are zero. That is a whole class of bug, an off by one at a size threshold, found by a generator and localised by shrinking.

## Building it as a CMake subdirectory

RapidCheck makes extensive use of C++11 and therefore requires a compliant compiler. The documentation names the compilers its continuous integration builds with, Clang 3.5, GCC 4.9 and Visual Studio 2015, and observes that any later versions should also work.

The build is a standard CMake project. If your own project uses CMake, you can have RapidCheck as a subdirectory by adding two lines to your `CMakeLists.txt`:

```cmake
add_subdirectory("path/to/rapidcheck")
target_link_libraries(my_target rapidcheck)
```

The documentation notes that this gives you both the linking and the include directories, which is the part that usually needs fiddling when a library is not designed for the add_subdirectory workflow.

The repository layout shows a fairly complete project rather than a header-only experiment. There is a `CMakeLists.txt` at the root, an `include/` directory, a `src/` directory, an `ext/` directory for bundled third party code, an `extras/` directory, a `test/` directory, a `doc/` directory that holds the user guide the README points to, and an `examples/` directory. The examples directory has its own CMakeLists.txt and subdirectories named `boost_test/`, `classify/`, `counter/`, `database/`, `gtest/`, `mapparser/` and others, which is a good map of the feature set since each one exercises a different part of the library.

There is also a `pkg-config.pc.cmake` file, so the library can be consumed by a build system that expects a pkg-config file, and a `.clang-format` file at the root, so the formatting of contributions is fixed by the project rather than by taste.

## The feature list, and what each entry buys you

The reasons to use RapidCheck are listed as features, and they are worth reading closely because each one addresses a specific complaint about other frameworks.

Writing properties in an imperative way that makes sense for C++ comes first. A lambda that takes generated arguments and asserts on them is ordinary C++ code, with no separate specification language to learn and no DSL to translate from what you meant.

Test case shrinking is the second entry, and it is the one already covered above.

Great support for STL types, including maps and sets, is third. This matters more than it sounds. A property test over a `std::map` needs a generator for maps, and the interesting part is not producing one but producing one whose shrinking behaviour is sensible, since a map has keys as well as values and a bad key in a big map is exactly the situation where naive shrinking leaves you with a large unreproducible case.

Advanced combinators for creating your own generators come next, which is how you extend the library to your own domain types.

Stateful testing based on commands in the vein of Erlang QuickCheck is the fifth feature, and it is the one that goes furthest. Instead of a single function, you model a sequence of commands against a state machine and the framework drives them, which is how you test something like a session protocol rather than a pure function.

Integration with popular testing frameworks such as Boost Test, Google Test and Google Mock is the last. That entry is pragmatic rather than glamorous, and it removes the objection that adopting a property testing framework means abandoning the runner you already have.

## The seed in the output is the reproducibility handle

One small feature deserves its own paragraph, because it is the difference between a bug report you can act on and one you cannot.

Every run prints the configuration it used, and in particular the random seed, in a line like `Using configuration: seed=9928307433081493900`.

That number is the entire random state of the run. Property based testing is, by construction, non-deterministic: the same code will fail on a Tuesday and pass on a Wednesday if the generated data differs. A framework that does not print the seed leaves you with a failure that may not recur, and a bug you cannot reproduce is a bug you cannot fix. Printing the seed means a colleague can run the same test with the same seed and get the same case, and the shrink is deterministic too, so the same ten shrinks lead back to the same minimal input.

This is a small design decision with an outsized effect on whether property testing survives contact with a real team. It also means the seed is a first class value you can pin in continuous integration if you want a reproducible run rather than a fresh one.

The other detail in the output is the test count. A passing run reports `OK, passed 100 tests`, so you can see how much of the input space was explored. The default count is a budget rather than a guarantee, and it is one of the knobs you would reach for when a property is slow or when you want more coverage before a release.

## Where property based testing is the wrong tool

Property based testing is a technique with a specific shape, and applying it where it does not fit produces nothing useful.

The first case is code with no invariants worth stating. If all you can say is that the function returns something, the property is vacuous and the framework will happily generate a hundred thousand meaningless inputs. A property needs a claim that could be false, and if you cannot write one, the technique is not ready for that code.

The second is code whose inputs are not generatable. A generator for a valid byte stream in a specific protocol may require so much domain knowledge that writing it costs more than the bug it finds. In that case, examples or fuzzing at the input boundary are the better tool.

The third is a shrinking behaviour that does not fit your domain. Shrinking works by generating a smaller value and checking whether the property still fails. If your type has an ordering where smaller does not mean simpler, the counterexample you get back may be a weird degenerate value rather than a revealing one. The documentation does not discuss custom shrinking for your own types, so if that is your situation, budget time for it or choose another approach.

The alternative to a framework is hand written example based tests plus a fuzzer, and the difference in approach is what each one can find. Example tests check the cases you thought of. A fuzzer explores the input space without a stated property, and finds crashes and hangs rather than violated invariants. A property framework sits between them, exploring with a claim attached, and its edge is that the shrink is only as good as the generator's notion of a smaller value.

That is also the honest case for a home grown approach: twenty lines of randomised testing with a fixed seed and a print of the failing input can cover a surprising amount of ground, and it has no dependency and no learning curve. The case for RapidCheck over that is shrinking and the generator library, since a hand written fuzzer gives you the failing input but not the minimal one, and a failing input with 4000 elements in it is a bad bug report.

The last practical note is about the project itself. With no GitHub releases and a last push on 2026-10-05, the library is maintained but not versioned in the usual way, so a production project should pin a specific commit and be prepared to rebase, and should read the user guide in the `doc/` directory rather than working only from the README.

## Conclusion

RapidCheck fits a C++ codebase that has invariants worth stating and example tests that keep missing the edge cases, because a property plus a generator explores the input space and the shrinking turns a failure into a case you can paste into a regression test. Skip it if your code has no invariants worth stating, since property based testing without a property is just random testing, and skip it if your toolchain predates C++11. Before you adopt it, check the shrink behaviour on your own types, since the smallest counterexample is only as good as the generator's notion of small, and note the maintenance picture, with the last push on 2026-10-05 and no GitHub releases, so pin a commit rather than tracking a branch.

## FAQ

### What is RapidCheck and what does it do?

It is a C++ property based testing framework inspired by QuickCheck. You state facts about your code, it generates random test data to find a case where the property does not hold, then shrinks that case to the smallest failing one.

### How do I use RapidCheck in a CMake project?

Add it as a subdirectory with `add_subdirectory("path/to/rapidcheck")` and link against it with `target_link_libraries(my_target rapidcheck)`, which sets up both the linking and the include directories.

### What compiler does RapidCheck need?

A C++11 compliant compiler. The documentation states that continuous integration builds with Clang 3.5, GCC 4.9 and Visual Studio 2015, and that later versions should also work.

### How do I reproduce a failing RapidCheck test?

Every run prints the configuration including the random seed, so you can rerun with the same seed and get the same generated data and the same shrunk counterexample.

### Which test frameworks can RapidCheck integrate with?

The feature list names Boost Test, Google Test and Google Mock, and the repository carries examples for each, alongside examples for stateful command based testing, classification and map parsing.

## Sources

- [emil-e/rapidcheck on GitHub](https://github.com/emil-e/rapidcheck)
- [Issues](https://github.com/emil-e/rapidcheck/issues)
- [License: BSD-2-Clause](https://github.com/emil-e/rapidcheck/blob/master/LICENSE)
- [README](https://github.com/emil-e/rapidcheck/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/emil-e-rapidcheck
