I want to test that MyException
is thrown in a certain case. EXPECT_THROW
is good here. But I also want to check the exception has a specific state
I recommend defining a new macro based on Mike Kinghan's approach.
#define ASSERT_EXCEPTION( TRY_BLOCK, EXCEPTION_TYPE, MESSAGE ) \
try \
{ \
TRY_BLOCK \
FAIL() << "exception '" << MESSAGE << "' not thrown at all!"; \
} \
catch( const EXCEPTION_TYPE& e ) \
{ \
EXPECT_EQ( MESSAGE, e.what() ) \
<< " exception message is incorrect. Expected the following " \
"message:\n\n" \
<< MESSAGE << "\n"; \
} \
catch( ... ) \
{ \
FAIL() << "exception '" << MESSAGE \
<< "' not thrown with expected type '" << #EXCEPTION_TYPE \
<< "'!"; \
}
Mike's TEST(foo_test,out_of_range)
example would then be
TEST(foo_test,out_of_range)
{
foo f;
ASSERT_EXCEPTION( { f.bar(111); }, std::out_of_range, "Out of range" );
}
which I think ends up being much more readable.
A colleague came up with the solution by just re-throwing the exception.
The knack: no need of extra FAIL() statements, just the two EXPECT... calls that test the bits you actually want: the exception as such and its value.
TEST(Exception, HasCertainMessage )
{
// this tests _that_ the expected exception is thrown
EXPECT_THROW({
try
{
thisShallThrow();
}
catch( const MyException& e )
{
// and this tests that it has the correct message
EXPECT_STREQ( "Cucumber overflow", e.what() );
throw;
}
}, MyException );
}
You can try Boost lightweight test:
#include <boost/detail/lightweight_test.hpp>
#include <stdexcept>
void function_that_would_throw(int x)
{
if (x > 0) {
throw std::runtime_error("throw!");
}
}
int main() {
BOOST_TEST_THROWS(function_that_would_throw(10), std::runtime_error);
boost::report_errors();
}
Jeff Langr describes a good approach in his book, Modern C++ Programming with Test-Driven Development:
If your [testing] framework does not support a single-line declarative assert that ensures an exception is thrown, you can use the following structure in your test:
TEST(ATweet, RequiresUserNameToStartWithAnAtSign) { string invalidUser("notStartingWith@"); try { Tweet tweet("msg", invalidUser); FAIL(); } catch(const InvalidUserException& expected) {} }
[...] You might also need to use the try-catch structure if you must verify any postconditions after the exception is thrown. For example, you may want to verify the text associated with the thrown exception object.
TEST(ATweet, RequiresUserNameToStartWithAtSign) { string invalidUser("notStartingWith@"); try { Tweet tweet("msg", invalidUser); FAIL(); } catch(const InvalidUserException& expected) { ASSERT_STREQ("notStartingWith@", expected.what()); } }
(p.95)
This is the approach I've used, and have seen in practice elsewhere.
Edit: As has been pointed out by @MikeKinghan, this doesn't quite match the functionality provided by EXPECT_THROW
; the test doesn't fail if the wrong exception is thrown. An additional catch
clause could be added to address this:
catch(...) {
FAIL();
}
I mostly second Lilshieste's answer but would add that you also should verify that the wrong exception type is not thrown:
#include <stdexcept>
#include "gtest/gtest.h"
struct foo
{
int bar(int i) {
if (i > 100) {
throw std::out_of_range("Out of range");
}
return i;
}
};
TEST(foo_test,out_of_range)
{
foo f;
try {
f.bar(111);
FAIL() << "Expected std::out_of_range";
}
catch(std::out_of_range const & err) {
EXPECT_EQ(err.what(),std::string("Out of range"));
}
catch(...) {
FAIL() << "Expected std::out_of_range";
}
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
My version; it produces the same output as EXPECT_THROW and just adds the string test:
#define EXPECT_THROW_MSG(statement, expected_exception, expected_what) \
try \
{ \
statement; \
FAIL() << "Expected: " #statement " throws an exception of type " #expected_exception \
".\n" \
" Actual: it throws nothing."; \
} \
catch (const expected_exception& e) \
{ \
EXPECT_EQ(expected_what, std::string{e.what()}); \
} \
catch (...) \
{ \
FAIL() << "Expected: " #statement " throws an exception of type " #expected_exception \
".\n" \
" Actual: it throws a different type."; \
}