GUnit icon indicating copy to clipboard operation
GUnit copied to clipboard

"Hi-Perf" (non-virtual) Dependency Injection

Open paulreimer opened this issue 6 years ago • 3 comments

If we are using the "Hi-Perf" Google.Mock feature https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#mocking-nonvirtual-methods

Then how does this affect the ideal use of GUnit?

  • [ ] Can we still use the automatic mock generation?
  • [ ] Can we still use GUnit.GMake? (since we are using derived class template args, not static_cast<iface&>)
  • [ ] Can we still use Boost.DI? (I know, a question for a different repo)

Or any other trade-offs from using non-virtual methods?

paulreimer avatar Dec 18 '17 01:12 paulreimer

Thanks @paulreimer for the questions

  • [ ] Can we still use the automatic mock generation?

    • Unfortunatelly not out of the box as automatic mock injection requres GMocks
    • You could still use it if you provide some sort of traits for the mocks, though
      template<class> struct Mock;
    
      template<>
      struct Mock<MockPacketStream> {
       MOCK_CONST_METHOD1(GetPacket, const Packet*(size_t packet_number));
       MOCK_CONST_METHOD0(NumberOfPackets, size_t());
      };
    

    And this automatic mocks injection when resolve a type T

    return Mock<T>();
    
  • [ ] Can we still use GUnit.GMake? (since we are using derived class template args, not static_cast<iface&>)

  • Sort of, as described above
  • [x] Can we still use Boost.DI? (I know, a question for a different repo)
  • Yes, no issues here

     example::example(ConcretePacketStream &); // no problems here, wiring not even needed
    

    With templates no issues too

     template<class T = class Stream>
     example::example(T stream);
    
    di::bind<class Stream>.to<ConcretePacketStream>()
    

One thing, which might be useful when using templates is to avoid writing macros, though. That can be done by defining an interface instead and using GMock, automatic mock injection and DI without loosing performance at all as it will be injected via templates.

struct IStreamJustForTesting {
  virtual ~IStreamJustForTesting () = default;
  virtual void AppendPacket(Packet* new_packet) = 0;
  virtual const Packet* GetPacket(size_t packet_number) const =0;
  virtual size_t NumberOfPackets() const = 0;
};
di::bind<class Stream>.to<GMock<IStreamJustForTesting>()

krzysztof-jusiak avatar Dec 18 '17 18:12 krzysztof-jusiak

OK, sounds good. I'm still trying to understand the first "automatic mock injection" part. Actually my question was more about the "automatic mock generation" part (e.g. avoiding macros), looks like it is possible as in the final example. So, at least we can define the interface in C++ (even if the concrete class doesn't inherit it) and avoid the MOCK_* macros (yay!).

Also just to check if I understand correctly, but in the final example then any mocks generated for IStreamJustForTesting will have dynamic dispatch (and use static_cast), but this only applies to the mocks and the production implementations do not need that interface? (and this is the price we pay -- at test time -- for avoiding the macros)

paulreimer avatar Dec 18 '17 18:12 paulreimer

Right, Macros might be avoided with interfaces and GMock.

Yes, exactly. No cost in production but dynamic dispatch in testing.

krzysztof-jusiak avatar Dec 18 '17 19:12 krzysztof-jusiak