Skip to content

Latest commit

 

History

History
221 lines (170 loc) · 10.4 KB

ToString.md

File metadata and controls

221 lines (170 loc) · 10.4 KB

String conversions

Contents

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:

  1. Input
  2. The TApprovals class has a template parameter StringConverter
  3. Approvals uses the default StringMaker
  4. 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;
}

snippet source | anchor

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 #ifdefs 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;
}

snippet source | anchor

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")
{
    Approvals::verifyAll("rectangles", getRectangles(), [](auto r, auto& os) {
        os << FormatRectangleForMultipleLines(r);
    });
}

snippet source | anchor

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_);
}

snippet source | anchor

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_);
    }
}

snippet source | anchor

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_);
    }
};

snippet source | anchor

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);

snippet source | anchor

Or you can create your own custom alias to use your customisation:

using MyApprovals = ApprovalTests::TApprovals<
    ApprovalTests::ToStringCompileTimeOptions<CustomToStringClass>>;

MyApprovals::verify(p);

snippet source | anchor

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

snippet source | anchor

See also


Back to User Guide