trompeloeil icon indicating copy to clipboard operation
trompeloeil copied to clipboard

Best way to use with Catch2 - BDD style

Open chenlijun99 opened this issue 6 years ago • 5 comments

When writing BDD tests with Catch2, we have something like this:

BarMocksCAPI bar_mock;

SCENARIO("foo") {
	GIVEN("foo in reset state") {
		WHEN("foo is initialized") {

			REQUIRE_CALL(bar_mock, bar_init());

			foo_init();

			THEN("foo should be initialized") {
				CHECK(foo_initialized() == 1)
			}
			THEN("bar should be initialized") {
				// how can we check here that the above REQUIRE_CALL has been met?
			}
		}
	}
}

What I would like to do is to give some mock call expectation a meaningful name, i.e. put it into a THEN block.

I know that I can achieve what I want by using NAMED_*

BarMocksCAPI bar_mock;

SCENARIO("foo") {
	GIVEN("foo in reset state") {
		WHEN("foo is initialized") {

			std::unique_ptr<trompeloeil::expectation> bar_should_be_initialized
				= NAMED_REQUIRE_CALL(bar_mock, bar_init());

			foo_init();

			THEN("foo should be initialized") {
				CHECK(foo_initialized() == 1)
			}
			THEN("bar should be initialized") {
				bar_should_be_initialized.reset();
			}
		}
	}
}

But I'm wondering whether there could be a tidier way to express this.

chenlijun99 avatar Jun 20 '19 15:06 chenlijun99

I have also been struggling with this issue.

richjohnson-wwt avatar Jun 16 '21 14:06 richjohnson-wwt

One thing you can do is to check if the expectation is satisfied.

REQUIRE(bar_should_be_initialized->is_satisfied());

I'm not sure if that is any better, though.

Another way is to use REQUIRE_NOTHROW(bar_should_be_initialized.reset());, but that doesn't really bring anything, other than maybe a more visually pleasing source code.

rollbear avatar Jun 16 '21 15:06 rollbear

I realize there are a few too many noexcept in the code, which causes some problems. Unfortunately Catch2 is doing something I don't quite understand in the fail case, so I need to investigate that too.

rollbear avatar Jun 16 '21 16:06 rollbear

If you use the catch2 adapter you get a reasonable error message. There's also an improved adapter on the develop branch.

#include <catch2/trompeloeil.hpp>

However, catch2 is behaving in a somewhat confusing way here. If you have a bug that bar_init() is not called from foo_init(), then this bug will be caught and reported twice (once for each THEN block). I don't think there is anything that can be done about that, since it is not really wrong, and quite fundamental to how catch2 works and also to how Trompeloeil works. There is no way to express that you only want this expectation checked in one of the THEN blocks.

rollbear avatar Jun 17 '21 05:06 rollbear

Thank you for your effort!

Now that I revisit this problem, maybe this could be a cleanier approach

BarMocksCAPI bar_mock;
BazMocksCAPI baz_mock;

SCENARIO("foo") {
	GIVEN("foo in reset state") {
		WHEN("foo is initialized") {

			std::unique_ptr<trompeloeil::expectation> expected_mock_call;

			THEN("bar should be initialized") {
				expected_mock_call = NAMED_REQUIRE_CALL(bar_mock, bar_init());
			}

			THEN("baz should be initialized") {
				expected_mock_call = NAMED_REQUIRE_CALL(baz_mock, baz_init());
			}

			foo_init();

			THEN("foo should be initialized") {
				CHECK(foo_initialized() == 1)
			}

                        expected_mock_call.reset()
		}
	}
}

And in case in each THEN block multiple mocking expectations need to be set, maybe a vector of unique_ptr could be used.

chenlijun99 avatar Jun 17 '21 13:06 chenlijun99