1

I'm trying to unit test one of my classes, but am struggling to return a rapidjson::Value from my mocked class.

I've looked around the internet, and haven't been successful so far. This is my last ditch attempt.

The code I'm running is the following;

class json_action_audit_generator_tests : public ::testing::Test 
{
    std::string test_focus_array = "[[11,12],[21,22],[31,32],[41,42],[51,52],[61,62],[71,72],[81,82],[91,92],[101,102],[111,112],[121,122],[131,132]]";

    Value create_json_key_from_enum(storage_enum mapped_value, rapidjson::Document& root)
    {
        return Value(storage_from_enum[mapped_value].c_str(), root.GetAllocator());
    }
}

TEST_F(json_action_audit_generator_tests, TestAddJsonFocusInformation)
{
    std::stringstream ss;
    ss << "{\"" << storage_from_enum[storage_enum::JsonFocus] << "\":"
        << "["
            << "[" << "11,12" << "]" << ","
            << "[" << "21,22" << "]" << ","
            << "[" << "31,32" << "]" << ","
            << "[" << "41,42" << "]" << ","
            << "[" << "51,52" << "]" << ","
            << "[" << "61,62" << "]" << ","
            << "[" << "71,72" << "]" << ","
            << "[" << "81,82" << "]" << ","
            << "[" << "91,92" << "]" << ","
            << "[" << "101,102" << "]" << ","
            << "[" << "111,112" << "]" << ","
            << "[" << "121,122" << "]" << ","
            << "[" << "131,132" << "]"
        << "]"
    << "}";

    rapidjson::Document root;
    root.SetObject();

    Value json_focus_information(rapidjson::kObjectType);
    add_json_string_member(root, root, create_json_key_from_enum(storage_enum::JsonFocus, root), test_focus_array.c_str());


    json_action_audit_generator testee(dashboard_action.get(), EKS.get(), json_focus_serializer.get());
----------
    EXPECT_CALL(*json_focus_serializer, get_focus_as_json_value()).WillOnce(Return(&json_focus_information));
----------

    testee.add_json_focus_information();
    EXPECT_EQ(ss.str(), testee.create_json_string());
};

I expect for a valid rapidjson::Value to be returned, but it currently doesn't compile and returns the following error

 C2248 - 'rapidjson::GenericValue<rapidjson::UTF8<char>,rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>>::GenericValue':
         cannot access private member declared in class 
         'rapidjson::GenericValue<rapidjson::UTF8<char>,rapidjson::MemoryPoolAllocator<rapidjson::CrtAllocator>>'

Which points me to the following in gmock-actions.h

template <typename R>
class ReturnAction {
 public:
  // Constructs a ReturnAction object from the value to be returned.
  // 'value' is passed by value instead of by const reference in order
  // to allow Return("string literal") to compile.
  explicit ReturnAction(R value) : value_(new R(std::move(value))) {}

  // This template type conversion operator allows Return(x) to be
  // used in ANY function that returns x's type.
  template <typename F>
  operator Action<F>() const {  // NOLINT
    // Assert statement belongs here because this is the best place to verify
    // conditions on F. It produces the clearest error messages
    // in most compilers.
    // Impl really belongs in this scope as a local class but can't
    // because MSVC produces duplicate symbols in different translation units
    // in this case. Until MS fixes that bug we put Impl into the class scope
    // and put the typedef both here (for use in assert statement) and
    // in the Impl class. But both definitions must be the same.
    typedef typename Function<F>::Result Result;
    GTEST_COMPILE_ASSERT_(
        !std::is_reference<Result>::value,
        use_ReturnRef_instead_of_Return_to_return_a_reference);
    static_assert(!std::is_void<Result>::value,
                  "Can't use Return() on an action expected to return `void`.");
    return Action<F>(new Impl<R, F>(value_));
  }

 private:
  // Implements the Return(x) action for a particular function type F.
  template <typename R_, typename F>
  class Impl : public ActionInterface<F> {
   public:
    typedef typename Function<F>::Result Result;
    typedef typename Function<F>::ArgumentTuple ArgumentTuple;

    // The implicit cast is necessary when Result has more than one
    // single-argument constructor (e.g. Result is std::vector<int>) and R
    // has a type conversion operator template.  In that case, value_(value)
    // won't compile as the compiler doesn't known which constructor of
    // Result to call.  ImplicitCast_ forces the compiler to convert R to
    // Result without considering explicit constructors, thus resolving the
    // ambiguity. value_ is then initialized using its copy constructor.
    explicit Impl(const std::shared_ptr<R>& value)
        : value_before_cast_(*value),
          value_(ImplicitCast_<Result>(value_before_cast_)) {}


----------
    Result Perform(const ArgumentTuple&) override { return value_; }
----------
   private:
    GTEST_COMPILE_ASSERT_(!std::is_reference<Result>::value,
                          Result_cannot_be_a_reference_type);
    // We save the value before casting just in case it is being cast to a
    // wrapper type.
    R value_before_cast_;
    Result value_;

    GTEST_DISALLOW_COPY_AND_ASSIGN_(Impl);
  };

Any help will be greatly appreciated! Sorry for the lack of syntax highlighting... I couldn't find out how. I will happily update the question if someone points me in the right direction.

UPDATE #1

Current interface used by mock class

#pragma once
#include "document.h"
#include <string>

class json_focus_serializer_interface {
public:
    json_focus_serializer_interface() = default;
    virtual ~json_focus_serializer_interface() = default;
    virtual rapidjson::Value get_focus_as_json_value() = 0;
    virtual std::string get_focus_as_json_string() = 0;
};

Current mock class definition

#pragma once
#include "json_focus_serializer_interface.h"
#include "gmock/gmock.h"


class mock_json_focus_serializer_interface : public json_focus_serializer_interface {
public:

    mock_json_focus_serializer_interface() = default;
    ~mock_json_focus_serializer_interface() = default;

    MOCK_METHOD(rapidjson::Value, get_focus_as_json_value, (), (override));
    MOCK_METHOD(std::string, get_focus_as_json_string, (), (override));
};
Quarra
  • 2,527
  • 1
  • 19
  • 27
Andy
  • 339
  • 3
  • 17
  • Unrelated to your problem, but why e.g. `<< "[" << "11,12" << "]" << ","` instead of `<< "[11,12],"`? I would understand the former if you use a loop instead (and why don't you use a loop?) but not for fixed strings. – Some programmer dude Oct 29 '19 at 12:13
  • Sure. I can update the string at a later date. I just want to get it to compile at the moment! Also, thanks for the style tip. – Andy Oct 29 '19 at 12:17
  • You are somehow trying to call a `GenericValue` constructor that you aren't allowed to touch. We can't tell from that error whether it's a copy constructor, move constructor or default constructor that is the problem. It would already help to know which exact line in `gmock-actions.h` this problem is at (and I'm fairly sure the error also links you to the correct place in the `GenericValue` definition, so that we can find out which constructor it attempts to use). – Max Langhof Oct 29 '19 at 12:25
  • Could you elaborate more what your intention is? What do you want to achieve with this code? – Superlokkus Oct 29 '19 at 13:12
  • @MaxLanghof - It's currently being called at **line 562** in gmock-actions.h. This eventually goes to **line 496** in gmock-internal-utils.h. Which looks like it leads to construction via a tuple of arguements. – Andy Oct 29 '19 at 13:30
  • @Andy I understand that it's hard to point to code lines since StackOverflow doesn't have line numbers, but please just mark the line in question in the code snippet with a comment. "Line 562" doesn't help for a 40 line code snippet. – Max Langhof Oct 29 '19 at 13:32
  • @Superlokkus - I'm trying to mock the return json_object which is returned by a composite class that I've injected as a dependency. – Andy Oct 29 '19 at 13:33
  • @MaxLanghof - I've updated the code snippets with horizontal rules. Hopefully that helps, and sorry for the inconvient line numbers! :-S – Andy Oct 29 '19 at 13:37
  • Can you please add the definition of your mock-class? I suspect you have a mismatch between the declared return type of `get_focus_as_json_value` and the pointer type you actually try to return with the `WillOnce(Return(...))` function. That is the only explanation I can think of for gmock trying to access members of `rapidsjon::GenericValue` here even though you're trying to return a `rapidjson::Value*`. – Corristo Nov 04 '19 at 14:05
  • @Corristo - Done. I've added an update with the interface and the mock definition. Hopefully it helps! – Andy Nov 07 '19 at 08:49

1 Answers1

3

The issue is that in EXPECT_CALL you try to return a rapidjson::Value* from a function that is declared to return a rapidjson::Value.

If rapidjson::Value had a proper copy constructor, all you'd need to do is to use WillOnce(Return(json_focus_information)) (note the missing ampersand).

However, since rapidjson does not allow you to create a copy of a rapidjson::Value without specifying an allocator things aren't as easy. One way I know of to return a copy of json_focus_information from your mock anyway is to use a ::testing::Invoke action instead of ::testing::Return like this:

auto return_json_focus_information = [&]() -> rapidjson::Value {
  static auto allocator = rapidjson::MemoryPoolAllocator{};
  return rapidjson::Value{json_focus_information, allocator};
}

EXPECT_CALL(*json_focus_serializer, get_focus_as_json_value())
    .WillOnce(::testing::Invoke(return_json_focus_information));

But be aware that in contrast to testing::Return the copy will be created at the time get_focus_as_json_value is called, not at the time the expectation is set up.

If, however, you don't even need the json_focus_information variable anywhere else in the test you could also use

auto return_json_focus_information = []() -> rapidjson::Value {
  return rapidjson::Value{rapidjson::kObjectType};
}

EXPECT_CALL(*json_focus_serializer, get_focus_as_json_value())
   .WillOnce(::testing::Invoke(return_json_focus_information));

and get rid of the original json_focus_information variable entirely.

Corristo
  • 4,911
  • 1
  • 20
  • 36
  • Awesome! This works, and the answer is fantastic! I completely understand why it wasn't working previously! You sir/madam, are officially the bee's knees! – Andy Nov 08 '19 at 13:52