- Introduction
- How Approval Tests converts your objects to strings
- Pass in a string
- Write a custom std::ostream operator (
<<
) - Specialize StringMaker
- Use
TApprovals<YourStringConvertingClass>
- See also
When you use Approval tests, any object you pass in is going to be converted to a string. This is how Approval Tests does that, and how you can customize that behavior.
The process from your input to the final output looks like this - You can customize the string at any of these 4 points:
- Input
- The TApprovals class has a template parameter StringConverter
- Approvals uses the default StringMaker
- StringMaker converts via std::ostream operator (<<)
Approval Tests can take a string, so it can be simple to simply create that string before you call verify()
.
This has the advantage of being straight-forward, but won't interact well with calls like verifyAll()
or Combination Approvals.
This is often done by providing an output operator (<<
) for types you wish to test.
For example:
friend std::ostream& operator<<(std::ostream& os, const Rectangle1& rectangle)
{
os << "[x: " << rectangle.x << " y: " << rectangle.y
<< " width: " << rectangle.width << " height: " << rectangle.height << "]";
return os;
}
You should put this function in the same namespace as your type, or the global namespace, and have it declared before including Approval's header. (This is particularly important if you are compiling with Clang.)
If including <iostream>
or similar is problematic, for example because your code needs to be compiled for embedded platforms, and you are tempted to surround it with #ifdef
s so that it only shows up in testing, we recommend that you use the template approach instead:
template <class STREAM>
friend STREAM& operator<<(STREAM& os, const Rectangle2& rectangle)
{
os << "[x: " << rectangle.x << " y: " << rectangle.y
<< " width: " << rectangle.width << " height: " << rectangle.height << "]";
return os;
}
Wrapper classes or functions can be used to provide additional output formats for types of data:
struct FormatRectangleForMultipleLines
{
explicit FormatRectangleForMultipleLines(const Rectangle3& rectangle_)
: rectangle(rectangle_)
{
}
const Rectangle3& rectangle;
friend std::ostream& operator<<(std::ostream& os,
const FormatRectangleForMultipleLines& wrapper)
{
os << "(x,y,width,height) = (" << wrapper.rectangle.x << ","
<< wrapper.rectangle.y << "," << wrapper.rectangle.width << ","
<< wrapper.rectangle.height << ")";
return os;
}
};
TEST_CASE("AlternativeFormattingCanBeEasyToRead")
{
ApprovalTests::Approvals::verifyAll(
"rectangles", getRectangles(), [](auto r, auto& os) {
os << FormatRectangleForMultipleLines(r);
});
}
Note The output operator (<<
) needs to be declared before Approval Tests. Usually this is handled by putting it in its own header file, and including that at the top of the test source code.
If you want to use something other than an output operator (<<
), one option is to create a specific specialization for StringMaker, for your specific type.
Here is an example:
template <>
std::string ApprovalTests::StringMaker::toString(const StringMakerPrintable& printable)
{
return "From StringMaker: " + std::to_string(printable.field1_);
}
With older compilers, you might need to make the namespace explicit, like this:
namespace ApprovalTests
{
template <> std::string StringMaker::toString(const StringMakerPrintable& printable)
{
return "From StringMaker: " + std::to_string(printable.field1_);
}
}
If you want to change a broader category of how strings are created, you can create your own string-maker class, and tell Approvals to use it, using the template mechanism.
Here is how you create your own string-maker class:
class CustomToStringClass
{
public:
template <typename T> static std::string toString(const T& printable)
{
return "From Template: " + std::to_string(printable.field1_);
}
};
However, this alone will not do anything. You now need to call a variation of Approvals that uses it. You can do this directly by:
ApprovalTests::TApprovals<
ApprovalTests::ToStringCompileTimeOptions<CustomToStringClass>>::verify(p);
Or you can create your own custom alias to use your customisation:
using MyApprovals = ApprovalTests::TApprovals<
ApprovalTests::ToStringCompileTimeOptions<CustomToStringClass>>;
MyApprovals::verify(p);
With MyApprovals::verify()
, we have not changed the behavior of Approvals::verify()
.
If, instead, you want to change the default string formatting, so that all calls to Approvals::verify()
and related methods will automatically use your new string formatter, you can override the default, by defining this macro before including the Approval Tests header.
#define APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER StringMaker
- Tips for Designing Strings
- How to Scrub Non-Deterministic Output
- How to Use the Fmt Library To Print Objects.