String conversions
Introduction
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.
How Approval Tests converts your objects to strings
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 (<<)
Pass in a string
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.
Write a custom std::ostream operator (<<
)
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;
}
(See snippet source)
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;
}
(See snippet source)
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);
});
}
(See snippet source)
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.
Specialize StringMaker
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_);
}
(See snippet source)
gcc 5 & 6
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_);
}
}
(See snippet source)
Use TApprovals<YourStringConvertingClass>
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_);
}
};
(See snippet source)
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);
(See snippet source)
Or you can create your own custom alias to use your customisation:
using MyApprovals = ApprovalTests::TApprovals<
ApprovalTests::ToStringCompileTimeOptions<CustomToStringClass>>;
MyApprovals::verify(p);
(See snippet source)
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
(See snippet source)