2

I'm using Catch2 to write my unit tests.

One thing I want to do is make sure that I'm catching the correct exception. I throw the same exceptions in many circumstances, so just knowing that I'm catching an std::logic_error doesn't prove that a specific instance of the exception was indeed caught.

Catch2 provides the REQUIRE_THROWS_MATCHES() macro for that purpose.

Here is an example of how I use it with the Equals matcher:

    CATCH_REQUIRE_THROWS_MATCHES(
                  std::make_shared<advgetopt::getopt>(
                            options_environment
                          , sub_argc
                          , sub_argv)
                , advgetopt::getopt_exception_logic
                , Catch::Matchers::Equals(
                          "section \"invalid::name\" includes a section separator (::) in \""
                        + options_filename
                        + "\". We only support one level."));

Only that does not compile unless I have a cast operator in my exceptions. In this case, that's easy enough since I have my own exception. But I'm wondering why the author of Catch2 thought of using a cast to std::string instead of using the what() function.

Here is my current base class exception definition:

class logic_exception_t
    : public std::logic_error
    , public exception_base_t
{
public:
    explicit                    logic_exception_t( std::string const & what, int const stack_trace_depth = STACK_TRACE_DEPTH );
    explicit                    logic_exception_t( char const *        what, int const stack_trace_depth = STACK_TRACE_DEPTH );

    virtual                     ~logic_exception_t() override {}

    virtual char const *        what() const throw() override;
                                operator std::string () const;
};

Here is the operator std::string () const function:

logic_exception_t::operator std::string () const
{
    return what();
}

Is there another way to satisfy the Catch2 requirement and allow for a transformation of an exception to an std::string without having to create a cast operator? I just don't like having a cast which could cause other problems down the road.

Note: I tried to make the cast explicit and Catch2 doesn't like it either. It just passes the exception to a function which expects an std::string.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156

2 Answers2

3

I ran into the same problem today; there is a half-way solution in the form of a Message matcher, documented in the Catch2 documentation.

REQUIRE_THROWS_MATCHES(fn(), SomeException, Message("Complete exception message"));

However, I would like to have a Contains like functionality. Given the Message implementation, it is trivial to implement it:

#include <catch2/matchers/catch_matchers.hpp>
#include <exception>
#include <string>

#include <string.h>

class ExceptionMessageContainsMatcher final : public MatcherBase<std::exception>
{
  std::string m_messagePart;

public:
  ExceptionMessageContainsMatcher(const std::string &messagePart) 
    : m_messagePart{messagePart}
  {}

  bool match(const std::exception &e) const {
    return ::strstr(e.what(), m_messagePart.data()) != nullptr;
  }

  std::string describe() const {
    return "exception message does not contain \"" + m_messagePart + '"';   
  }
};

ExceptionMessageContainsMatcher MessageContains(const std::string &message) { 
  return {message};
}

This allows you to write:

REQUIRE_THROWS_MATCHES(fn(), SomeException, MessageContains("part of the message"));
Ton van den Heuvel
  • 10,157
  • 6
  • 43
  • 82
0

You can actually define your own watcher, so I decided to write a watcher that would take an exception for its match() function. This works without the casting to std::string!

namespace Catch
{
namespace Matchers
{


class ExceptionWatcher
    : public MatcherBase<std::exception>
{
public:
    ExceptionWatcher(std::string const & expected_message)
        : m_expected_message(expected_message)
    {
    }

    /** \brief Check whether we got a match.
     *
     * This function compares the expected string with the actual exception
     * what() output.
     */
    bool match(std::exception const & e) const override
    {
        return e.what() == m_expected_message;
    }

    /** \brief Describe this matcher.
     *
     * This function produces a string describing what this matcher does.
     *
     * \return The description of this matcher.
     */
    virtual std::string describe() const override
    {
        return "compare the exception what() message with \""
             + m_expected_message
             + "\".";
    }

private:
    std::string     m_expected_message = std::string();
};


inline ExceptionWatcher ExceptionMessage(std::string const & expeted_message)
{
    return ExceptionWatcher(expeted_message);
}



}
// Matchers namespace
}
// Catch namespace
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156