Search code examples
c++unit-testingmockinggooglemock

Why does GMOCK object not return values set by EXPECT_CALL in dependency injection


I have the following object that I want to mock:

class Esc {
 public:
  Esc() = default;
  virtual ~Esc() {}
  virtual int GetMaxPulseDurationInMicroSeconds() const noexcept{
    return 100;
  }
};

I wrote this mock:

class MockEsc : public Esc {
 public:
  MockEsc(){}
  MockEsc(const MockEsc&){}
  MOCK_METHOD(int, GetMaxPulseDurationInMicroSeconds, (), (const, noexcept, override));
};

And this is the unit under test which calls the aforementioned Esc.GetMaxPulseDurationInMicroSeconds() method

class LeTodar2204{
 public:
  LeTodar2204() = delete;
  explicit LeTodar2204(std::unique_ptr<Esc> esc) : esc_(std::move(esc)){}
  int CallingMethod(){
    int a = esc_->GetMaxPulseDurationInMicroSeconds();
    return a;
  }

 private:
  std::unique_ptr<Esc> esc_;
};

In the SetUp of my testfixture I want to set my mock to return a 1 and inject it into the unit under test.

class Letodar2204Tests : public ::testing::Test {
 protected:
  Letodar2204Tests() {}
  virtual void SetUp() {
    EXPECT_CALL(esc_, GetMaxPulseDurationInMicroSeconds()).WillOnce(::testing::Return(1));
    unit_under_test_ = std::make_unique<LeTodar2204>(std::make_unique<MockEsc>(esc_));
  }

  MockEsc esc_;
  std::unique_ptr<LeTodar2204> unit_under_test_;
};

Now in the test I call the method that should call the mocked method, but GetMaxPulseDurationInMicroSeconds of my mocked object only returns 0 (aka the default value) and warns me about the uninteresting function call.

This is the test

TEST_F(Letodar2204Tests, get_pulse_duration) {
  EXPECT_CALL(esc_, GetMaxPulseDurationInMicroSeconds()).WillOnce(::testing::Return(1));
  auto duration = unit_under_test_->CallingMethod();
  ASSERT_EQ(duration, 1);
}

What am I missing?


Solution

  • Because you are effectively assigning expectation to an object which you'll be copying anyway. esc_ default ctor is going to be called once Letodar2204Tests is instantiated. Now, in SetUp you assign an expectation on class' field esc_, and then, basing on esc_ create a brand new object (on the heap, using make_unique) using its copy-ctor. Expectations are not going to be magically copied as well. I believe you should store an unique_ptr<MockEsc> esc_ as a class' field, instantiate it on heap within SetUp and inject to LeTodar:

    class Letodar2204Tests : public ::testing::Test {
     protected:
      Letodar2204Tests() {}
      virtual void SetUp() {
        esc_ = std::make_unique<MockEsc>();
        EXPECT_CALL(*esc_, GetMaxPulseDurationInMicroSeconds()).WillOnce(::testing::Return(1));
        unit_under_test_ = std::make_unique<propulsion::LeTodar2204>(esc_);
      }
    
      std::unique_ptr<MockEsc> esc_;
      std::unique_ptr<propulsion::LeTodar2204> unit_under_test_;
    };
    

    Here, you are implicitly calling copy-ctor: std::make_unique<MockEsc>(esc_)

    You could do a simple test: mark copy ctor in MockEsc 'deleted' and you'll see that your project no longer compiles (at least it shouldn't be :D)