mirror of
https://github.com/google/googletest.git
synced 2025-03-09 16:26:03 +00:00
Add matchers for testing exception properties
This PR adds matchers that accept a callable and verify that when invoked, it throws an exception with the given type and properties. Fixes #952
This commit is contained in:
parent
e6e2d3b761
commit
9ac4cd0f49
@ -4725,6 +4725,140 @@ PolymorphicMatcher<internal::variant_matcher::VariantMatcher<T> > VariantWith(
|
|||||||
internal::variant_matcher::VariantMatcher<T>(matcher));
|
internal::variant_matcher::VariantMatcher<T>(matcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if GTEST_HAS_EXCEPTIONS
|
||||||
|
|
||||||
|
// Anything inside the 'internal' namespace IS INTERNAL IMPLEMENTATION
|
||||||
|
// and MUST NOT BE USED IN USER CODE!!!
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template <typename Err>
|
||||||
|
class ExceptionMatcherImpl {
|
||||||
|
public:
|
||||||
|
ExceptionMatcherImpl(Matcher<const Err&> matcher)
|
||||||
|
: matcher_(std::move(matcher)) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void DescribeTo(::std::ostream* os) const {
|
||||||
|
*os << "throws an exception of type " << GetTypeName<Err>();
|
||||||
|
if (matcher_.GetDescriber() != nullptr) {
|
||||||
|
*os << " which ";
|
||||||
|
matcher_.DescribeTo(os);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescribeNegationTo(::std::ostream* os) const {
|
||||||
|
*os << "not (";
|
||||||
|
DescribeTo(os);
|
||||||
|
*os << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool MatchAndExplain(T&& x, MatchResultListener* listener) const {
|
||||||
|
try {
|
||||||
|
(void)(std::forward<T>(x)());
|
||||||
|
} catch (const Err& err) {
|
||||||
|
*listener << "throws an exception of type " << GetTypeName<Err>();
|
||||||
|
if (matcher_.GetDescriber() != nullptr) {
|
||||||
|
*listener << " ";
|
||||||
|
return matcher_.MatchAndExplain(err, listener);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& err) {
|
||||||
|
#if GTEST_HAS_RTTI
|
||||||
|
*listener << "throws an exception of type "
|
||||||
|
<< GetTypeName(typeid(err)) << " ";
|
||||||
|
#else
|
||||||
|
*listener << "throws an std::exception-derived error ";
|
||||||
|
#endif
|
||||||
|
*listener << "with description \"" << err.what() << "\"";
|
||||||
|
return false;
|
||||||
|
} catch (...) {
|
||||||
|
*listener << "throws an exception of some other type";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*listener << "does not throw any exception";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Matcher<const Err&> matcher_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
// Throws()
|
||||||
|
// Throws(exceptionMatcher)
|
||||||
|
// ThrowsMessage(messageMatcher)
|
||||||
|
// ThrowsMessageHasSubstr(message)
|
||||||
|
//
|
||||||
|
// This matcher accepts a callable and verifies that when invoked, it throws
|
||||||
|
// an exception with the given type and properties.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// EXPECT_THAT(
|
||||||
|
// []() { throw std::runtime_error("message"); },
|
||||||
|
// Throws<std::runtime_error>());
|
||||||
|
//
|
||||||
|
// EXPECT_THAT(
|
||||||
|
// []() { throw std::runtime_error("message"); },
|
||||||
|
// ThrowsMessage<std::runtime_error>(HasSubstr("message")));
|
||||||
|
//
|
||||||
|
// EXPECT_THAT(
|
||||||
|
// []() { throw std::runtime_error("message"); },
|
||||||
|
// ThrowsMessageHasSubstr<std::runtime_error>("message"));
|
||||||
|
//
|
||||||
|
// EXPECT_THAT(
|
||||||
|
// []() { throw std::runtime_error("message"); },
|
||||||
|
// Throws
|
||||||
|
|
||||||
|
template <typename Err>
|
||||||
|
PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>>
|
||||||
|
Throws() {
|
||||||
|
return MakePolymorphicMatcher(
|
||||||
|
internal::ExceptionMatcherImpl<Err>{
|
||||||
|
Matcher<const Err&>{}});
|
||||||
|
}
|
||||||
|
template <typename Err, typename ExceptionMatcher>
|
||||||
|
PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>>
|
||||||
|
Throws(const ExceptionMatcher& exceptionMatcher) {
|
||||||
|
// Using matcher cast allows users to pass a matcher of a more broad type.
|
||||||
|
// For example user may want to pass Matcher<std::exception>
|
||||||
|
// to Throws<std::runtime_error>, or Matcher<int64> to Throws<int32>.
|
||||||
|
return MakePolymorphicMatcher(
|
||||||
|
internal::ExceptionMatcherImpl<Err>{
|
||||||
|
SafeMatcherCast<const Err&>(exceptionMatcher)});
|
||||||
|
}
|
||||||
|
template <typename Err, typename MessageMatcher>
|
||||||
|
PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>>
|
||||||
|
ThrowsMessage(const MessageMatcher& messageMatcher) {
|
||||||
|
static_assert(
|
||||||
|
std::is_base_of<std::exception, Err>::value,
|
||||||
|
"expected an std::exception-derived class");
|
||||||
|
// We cast matcher to std::string so that we have string semantics instead of
|
||||||
|
// pointer semantics. With this cast, we can accept matchers that match types
|
||||||
|
// that're constructible from strings. Also, we can accept raw string
|
||||||
|
// literals, e.g. ThrowsMessage("message").
|
||||||
|
return MakePolymorphicMatcher(
|
||||||
|
internal::ExceptionMatcherImpl<Err>{
|
||||||
|
Property("description", &std::exception::what,
|
||||||
|
MatcherCast<std::string>(messageMatcher))});
|
||||||
|
}
|
||||||
|
template <typename Err, typename Message = std::string>
|
||||||
|
PolymorphicMatcher<internal::ExceptionMatcherImpl<Err>>
|
||||||
|
ThrowsMessageHasSubstr(const internal::StringLike<Message>& message) {
|
||||||
|
static_assert(
|
||||||
|
std::is_base_of<std::exception, Err>::value,
|
||||||
|
"expected an std::exception-derived class");
|
||||||
|
return MakePolymorphicMatcher(
|
||||||
|
internal::ExceptionMatcherImpl<Err>{
|
||||||
|
Property("description", &std::exception::what, HasSubstr(message))});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GTEST_HAS_EXCEPTIONS
|
||||||
|
|
||||||
// These macros allow using matchers to check values in Google Test
|
// These macros allow using matchers to check values in Google Test
|
||||||
// tests. ASSERT_THAT(value, matcher) and EXPECT_THAT(value, matcher)
|
// tests. ASSERT_THAT(value, matcher) and EXPECT_THAT(value, matcher)
|
||||||
// succeed if and only if the value matches the matcher. If the assertion
|
// succeed if and only if the value matches the matcher. If the assertion
|
||||||
|
@ -8117,6 +8117,188 @@ TEST(MatcherPMacroTest, WorksOnMoveOnlyType) {
|
|||||||
EXPECT_THAT(p, Not(UniquePointee(2)));
|
EXPECT_THAT(p, Not(UniquePointee(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if GTEST_HAS_EXCEPTIONS
|
||||||
|
|
||||||
|
TEST(ThrowsTest, Describe) {
|
||||||
|
Matcher<void (*)()> matcher = Throws<std::runtime_error>();
|
||||||
|
std::stringstream ss;
|
||||||
|
matcher.DescribeTo(&ss);
|
||||||
|
auto explanation = ss.str();
|
||||||
|
EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThrowsTest, Success) {
|
||||||
|
Matcher<void (*)()> matcher = Throws<std::runtime_error>();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_TRUE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { throw std::runtime_error("error message"); }, &listener));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("std::runtime_error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThrowsTest, FailWrongType) {
|
||||||
|
Matcher<void (*)()> matcher = Throws<std::runtime_error>();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { throw std::logic_error("error message"); }, &listener));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("std::logic_error"));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("\"error message\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThrowsTest, FailWrongTypeNonStd) {
|
||||||
|
Matcher<void (*)()> matcher = Throws<std::runtime_error>();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { throw 10; }, &listener));
|
||||||
|
EXPECT_THAT(
|
||||||
|
listener.str(),
|
||||||
|
testing::HasSubstr("throws an exception of some other type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ThrowsTest, FailNoThrow) {
|
||||||
|
Matcher<void (*)()> matcher = Throws<std::runtime_error>();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { (void)0; }, &listener));
|
||||||
|
EXPECT_THAT(
|
||||||
|
listener.str(),
|
||||||
|
testing::HasSubstr("does not throw any exception"));
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThrowsPredicateTest: public TestWithParam<Matcher<void (*)()>> {};
|
||||||
|
|
||||||
|
TEST_P(ThrowsPredicateTest, Describe) {
|
||||||
|
Matcher<void (*)()> matcher = GetParam();
|
||||||
|
std::stringstream ss;
|
||||||
|
matcher.DescribeTo(&ss);
|
||||||
|
auto explanation = ss.str();
|
||||||
|
EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
|
||||||
|
EXPECT_THAT(explanation, testing::HasSubstr("error message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ThrowsPredicateTest, Success) {
|
||||||
|
Matcher<void (*)()> matcher = GetParam();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_TRUE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { throw std::runtime_error("error message"); }, &listener));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("std::runtime_error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ThrowsPredicateTest, FailWrongType) {
|
||||||
|
Matcher<void (*)()> matcher = GetParam();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { throw std::logic_error("error message"); }, &listener));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("std::logic_error"));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("\"error message\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ThrowsPredicateTest, FailWrongTypeNonStd) {
|
||||||
|
Matcher<void (*)()> matcher = GetParam();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { throw 10; }, &listener));
|
||||||
|
EXPECT_THAT(
|
||||||
|
listener.str(),
|
||||||
|
testing::HasSubstr("throws an exception of some other type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ThrowsPredicateTest, FailWrongMessage) {
|
||||||
|
Matcher<void (*)()> matcher = GetParam();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { throw std::runtime_error("wrong message"); }, &listener));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("std::runtime_error"));
|
||||||
|
EXPECT_THAT(listener.str(), testing::HasSubstr("wrong message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(ThrowsPredicateTest, FailNoThrow) {
|
||||||
|
Matcher<void (*)()> matcher = GetParam();
|
||||||
|
StringMatchResultListener listener;
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.MatchAndExplain(
|
||||||
|
[]() { (void)0; }, &listener));
|
||||||
|
EXPECT_THAT(
|
||||||
|
listener.str(),
|
||||||
|
testing::HasSubstr("does not throw any exception"));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_SUITE_P(AllMessagePredicates, ThrowsPredicateTest,
|
||||||
|
::testing::Values(
|
||||||
|
static_cast<Matcher<void (*)()>>(
|
||||||
|
Throws<std::runtime_error>(
|
||||||
|
Property(&std::exception::what, HasSubstr("error message")))),
|
||||||
|
static_cast<Matcher<void (*)()>>(
|
||||||
|
ThrowsMessage<std::runtime_error>(HasSubstr("error message"))),
|
||||||
|
static_cast<Matcher<void (*)()>>(
|
||||||
|
ThrowsMessageHasSubstr<std::runtime_error>("error message"))));
|
||||||
|
|
||||||
|
// Tests that Throws<E1>(Matcher<E2>{}) compiles even when E2 != const E1&.
|
||||||
|
TEST(ThrowsPredicateCompilesTest, ExceptionMatcherAcceptsBroadType) {
|
||||||
|
{
|
||||||
|
Matcher<std::exception> inner =
|
||||||
|
Property(&std::exception::what, HasSubstr("error message"));
|
||||||
|
Matcher<void (*)()> matcher = Throws<std::runtime_error>(inner);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
matcher.Matches([]() { throw std::runtime_error("error message"); }));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.Matches([]() { throw std::runtime_error("wrong message"); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Matcher<uint64_t> inner = Eq(10);
|
||||||
|
Matcher<void (*)()> matcher = Throws<uint32_t>(inner);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
matcher.Matches([]() { throw (uint32_t)10; }));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.Matches([]() { throw (uint32_t)11; }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that ThrowsMessage("message") is equivalent
|
||||||
|
// to ThrowsMessage(Eq<std::string>("message")).
|
||||||
|
TEST(ThrowsPredicateCompilesTest, MessageMatcherAcceptsNonMatcher) {
|
||||||
|
Matcher<void (*)()> matcher = ThrowsMessage<std::runtime_error>(
|
||||||
|
"error message");
|
||||||
|
EXPECT_TRUE(
|
||||||
|
matcher.Matches(
|
||||||
|
[]() { throw std::runtime_error("error message"); }));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.Matches(
|
||||||
|
[]() { throw std::runtime_error("wrong error message"); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that ThrowsMessageHasSubstr accepts types that're
|
||||||
|
// explicitly-convertible to std::string.
|
||||||
|
TEST(ThrowsPredicateCompilesTest, StringLikeMessage) {
|
||||||
|
struct SomeCustomString {
|
||||||
|
std::string inner;
|
||||||
|
|
||||||
|
// Note: explicit conversion.
|
||||||
|
explicit operator std::string() const {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Matcher<void (*)()> matcher = ThrowsMessageHasSubstr<std::runtime_error>(
|
||||||
|
SomeCustomString{"error message"});
|
||||||
|
EXPECT_TRUE(
|
||||||
|
matcher.Matches(
|
||||||
|
[]() { throw std::runtime_error("error message"); }));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
matcher.Matches(
|
||||||
|
[]() { throw std::runtime_error("wrong message"); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GTEST_HAS_EXCEPTIONS
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace gmock_matchers_test
|
} // namespace gmock_matchers_test
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
|
Loading…
x
Reference in New Issue
Block a user