rollbear / trompeloeil

Header only C++14 mocking framework
Boost Software License 1.0
802 stars 85 forks source link

Setting expectations in a function fails #311

Open ninkibah opened 12 months ago

ninkibah commented 12 months ago

I have a very large class I want to mock, and I created a function to set all these expectations, which I then call from the test.

Sadly, this always fails. It would appear that the ALLOW_CALL invocations create local expectation objects which need to be alive when the mock is used.

I appreciate that this might be a very large change to make to the library, but if not, this should be documented in the FAQ.

Here's my sample code:

#include <catch2/catch_test_macros.hpp>
#include <catch2/trompeloeil.hpp>

struct Real {
  virtual int func() const = 0;
  virtual ~Real() = default;
};

struct Mock : public Real {
  MAKE_MOCK0(func, int(), const);
  ~Mock() override = default;
};

void setupExpectations(Mock& obj) {
  ALLOW_CALL(obj, func()).RETURN(1729);
}

TEST_CASE("Expectations in their own function") {
  Mock mock;
  setupExpectations(mock);

  REQUIRE(mock.func() == 1729); // Throws exception saying No match for call of func with signature int() with.
}

TEST_CASE("Expectations in test") {
  Mock mock;
  ALLOW_CALL(mock, func()).RETURN(1729);

  REQUIRE(mock.func() == 1729); // This works fine, of course!
}
rollbear commented 12 months ago

All expectations set up are valid in the scope of the set up, so ALLOW_CALL() in setupExpectations() is alive until the end of the function setupExpectations() and therefore no longer available in the line of the comment.

You can use NAMED_ALLOW_CALL() to bind the expectation to a variable that you choose maintain the lifetime of.

ninkibah commented 12 months ago

Thanks for the quick answer.

I went looking for examples with NAMED_ALLOW_CALL and didn't find any, but I guess it would look something like the folowing:

struct Real {
  virtual int func() const = 0;
  virtual ~Real() = default;
};

struct Mock : public Real {
  MAKE_MOCK0(func, int(), const);
  ~Mock() override = default;
};

auto setupExpectations(Mock& obj) {
  return NAMED_ALLOW_CALL(obj, func()).RETURN(1729);
}

TEST_CASE("Expectations in their own function") {
  Mock mock;
  auto expectation = setupExpectations(mock);

  REQUIRE(mock.func() == 1729); // Throws exception saying No match for call of func with signature int() with.
}

However, my mock needs 10-15 expectations (don't ask), so I guess I will have to create a vector of std::vector<std::unique_ptr<expectation>> and push each NAMED_ALLOW_CALL into it, and then return the vector. This is a bit ugly. I suspect that people rarely use this feature.

Tomorrow, I'll make a PR for a documentation change to help other people.

rollbear commented 12 months ago

That looks right. I've done it that way too. The vector is nice. I'm a bit surprised that you didn't find any examples of NAMED_ALLOW_CALL. Here's the entry in the reference manual: https://github.com/rollbear/trompeloeil/blob/main/docs/reference.md#NAMED_ALLOW_CALL, with an example. I guess there's not a good enough entry point to find it?

ninkibah commented 12 months ago

I was looking in the cookbook, which seemed the best place to start. All the examples there were ALLOW_CALL or REQUIRE_CALL, so I presumed that's what I should use. I'll add a section there tomorrow, when I have time.

ninkibah commented 12 months ago

Good to see that I hadn't found missing functionality in your library :-)

rollbear commented 10 months ago

Is there something I should do here, or can I close the issue?