1

I have a class defined as:

class Foo {
public:
    Foo();
private:
    std::unique_ptr<Bar> _bar;
}

Now I want to unit test class Foo by mocking the class Bar and EXPECT_CALL some functions there.

Bar is currently initialized as like:

Foo:Foo(...) : _bar( new Bar(...) ) {
    ...
}

Can I somehow inject my mocked class without some preprocessor hacks or structure this code differently to be able to do it?

idontknow
  • 17
  • 4
  • 1
    Uh, no. Your class is not prepared for dependency injection. You cannot mock `Bar` without modifying `Foo` to accept `Bar` from the outside or having a `friend` of the test class. – Yksisarvinen Jan 26 '22 at 13:29
  • I know this probably isn't exactly what you're looking for, but this (among other reasons) is exactly why I made [Mimicc](https://mimicc.dev/). I often want to test things that don't neatly fit into a dependency injection paradigm. You can use Mimicc alongside Google Test, but you will have to use the Mimicc API for setting up expect/return calls. – Jon Reeves Jan 26 '22 at 19:49

1 Answers1

1

The short answer is no, you can't do it unless you make some minor changes to your class.

EXPECT_CALL requires access to the mock object:

EXPECT_CALL(mock_object, method_name(matchers...))

So how can you call it without any reference to mock_object when it is a private member without any getters and no friends?

You do need to somehow provide external access to _bar so that EXPECT_CALL can use it to check your calls on _bar.

There are several ways to do this, but they all require changing your code:

  1. Make _bar public
  2. Dependency injection
  3. Add a getter of _bar to Foo
  4. Make your test a friend of Foo

It's common to do this by dependency injection. For example, you can add a new constructor like this:

class Foo {
 public:
  Foo() : _bar(new Bar()) {}
  Foo(Bar* bar) : _bar(bar) {}

  int DoSomething(int a) { return _bar->f1(a); }

  Bar *getBar() { return _bar.get(); } // Getter was added.

 private:
  std::unique_ptr<Bar> _bar;
};

And here is an example on how you can do this by adding a getter function to Foo:

class Bar {
 public:
  MOCK_METHOD(int, f1, (int), ());
};

class Foo {
 public:
  Foo() : _bar(new Bar()) {}
  int DoSomething(int a) { return _bar->f1(a); }

 // A getter was added (Albeit not ideal since it exposes the raw pointer).  
 Bar *getBar() { return _bar.get(); } 

 private:
  std::unique_ptr<Bar> _bar;
};

TEST(FooTest, TestBar) {
  Foo foo;
  EXPECT_CALL(*(foo.getBar()), f1(2)).WillOnce(Return(4));

  auto result = foo.DoSomething(2);
  EXPECT_EQ(result, 4);
}

Working example here: https://godbolt.org/z/1cdq7Po1T

Ari
  • 7,251
  • 11
  • 40
  • 70
  • 1
    Assumption that `Bar` is a GoogleMock class is rather far-fetched. Hard coding a GoogleMock class in class under test would be a very strange thing to do (because then you can only ever test it and not use it in production code). Because of that, your second example is not very useful, because you still need to use dependency injection to actually replace real life `Bar` with `BarMock`. Also points 1. and 3. of your proposed solutions rely on fact that `Bar` is already a mock class, but it cannot reasonably be one. – Yksisarvinen Jan 27 '22 at 02:16
  • 1
    I've already fought the issue trying to inject unique_ptr, see https://stackoverflow.com/questions/40508033/dependency-injection-with-unique-ptr-to-mock. IMHO adding getters or special constructors just for tests is a no-go. – Quarra Jan 27 '22 at 07:59
  • 1
    @Yksisarvinen: No, you don't always have to use dependency injection for replacing `Bar` with `MockBar` provided that `Foo` is templatized. This is an alternative solution to mocking classes that are not virtual, and it is used in production.https://godbolt.org/z/s9cEYPqab. I simplified the example in my solution because because this part was not the main point of the OP's question as I understood it. – Ari Jan 27 '22 at 17:34
  • 2
    @Quarra: In the link that you posted - Great post btw! - your class `Foo` already has a constructor that accepts `Bar`: ```out.reset(new Foo(std::move(ptr)));```. The OP's code doesn't seem to have such a constructor, and it looks like the existing one just creates a new `Bar` itself as a private member. I don't know how else you can access it without a getter or a different constructor like your own example. – Ari Jan 27 '22 at 17:44
  • @Ari maybe I wasn't clear enough: IMO it's best to use dependency injection because it clearly shows how the classes are cooperating with each other (what depends on what, what is using what, which one owns which etc.) and is the most testable approach (no special methods needed == less code to maintain). – Quarra Jan 28 '22 at 07:45