1

I'm using Hippo Mocks to great success, but I have a situation that I can't quite figure out how to set up properly. The code under test looks sorta like this:

auto firstName = record.at("firstName").getValue();
auto lastName = record.at("lastName").getValue();

Where IRecord::at() returns a IColumn&, which has a pure virtual getValue() method. I'm trying to test my code with mocked versions of Irecord and IColumn:

auto mockRec = mocks.InterfaceMock<IRecord>();
auto mockCol = mocks.InterfaceMock<IColumn>();

I can set up the expectations for firstName just fine:

mocks.OnCall(mockRec, IRecord::at).With("firstName").Return(std::ref(*mockCol));
mocks.OnCall(mockCol, IColumn::getValue).Return(std::string("John")));

But I would like to reuse the IColumn mock for the next expectation for lastName.

mocks.OnCall(mockRec, IRecord::at).With("lastName").Return(std::ref(*mockCol));
mocks.OnCall(mockCol, IColumn::getValue).Return(std::string("Doe")));

But when I run this, Hippo Mocks returns "John" for both getValue() calls.

Following along with the tutorial, I tried to restrict the order of "firstName" and "lastName" calls:

auto& firstCall = mocks.OnCall(mockRec, IRecord::at).With("firstName").Return(std::ref(*mockCol));
mocks.OnCall(mockCol, IColumn::getValue).After(firstCall).Return(std::string("John")));

auto& lastCall = mocks.OnCall(mockRec, IRecord::at).With("lastName").Return(std::ref(*mockCol));
mocks.OnCall(mockCol, IColumn::getValue).After(lastCall).Return(std::string("Doe")));

But I still get "John" for both getValue() calls.

Q: Is it possible to reuse the IColumn interface and tell Hippo Mocks to return different values with each getValue() call like I need, or am I stuck creating a separate IColumn mock for each parameter? Note: My actual implementation will have more than two parameters, so reusing the IColumn mock reduces a lot of setup for each unit test.

Bret Kuhns
  • 4,034
  • 5
  • 31
  • 43
  • To be quite honest, how should it know which answer you're expecting? In both cases you tell it that you want to return "John" for the next 0..N calls... One option is to make the getValue an ExpectCall with .After(the at call). That way it can only be called once and will only be allowed when the OnCall for at has been done at least once. – dascandy Jan 19 '14 at 22:21
  • @dascandy "how should it know which answer you're expecting?" Because I used the `.After()` in an attempt say "first return string A, next time it's called return string B, etc". That didn't seem to work. Making it an `ExpectCall` is also a problem because I was trying to put this in a general `setupMocks()` for a set of unit tests, where some tests aren't required to call `getValue()`. Thanks for the help though (I know you're HippoMocks author). – Bret Kuhns Jan 21 '14 at 13:59
  • Hm... makes sense to think of it like that. After only specifies a precondition though; it is not an immediate order requirement. OnCall also specifies that it is valid for the next N calls, not just one call. I know that Thomas added a more configurable limit specification to it but I'm not sure how to use it and I don't see a test for it. That should allow you to do what you want though. – dascandy Jan 21 '14 at 19:57

1 Answers1

2

I'm not sure what the problem in your case is, but when I run the following code with the version form the git repoitory

struct IColumn {
    virtual std::string getValue() = 0;
}; 

struct IRecord {
    virtual IColumn& at( std::string ) = 0;
};

void main()
{
    MockRepository mocks;

    auto mockRec = mocks.Mock<IRecord>();
    auto mockCol = mocks.Mock<IColumn>();

    auto& firstCall = mocks.OnCall(mockRec, IRecord::at).With("firstName").Return(std::ref(*mockCol));
    mocks.OnCall(mockCol, IColumn::getValue).After(firstCall).Return(std::string("John"));

    auto& lastCall = mocks.OnCall(mockRec, IRecord::at).With("lastName").Return(std::ref(*mockCol));
    mocks.OnCall(mockCol, IColumn::getValue).After(lastCall).Return(std::string("Doe"));

    std::cout << mockRec->at("firstName").getValue() << " " 
              << mockRec->at("lastName").getValue() << "\n";
}

I get the correct output.

John Doe

I find that I almost always use

mocks.autoExpect = false;

but in this case it doesn't make any difference.

Edit:

If you require more flexibility, you can do something like this:

std::vector<IColumn*> cols;

cols.push_back( mocks.Mock<IColumn>() );
cols.push_back( mocks.Mock<IColumn>() );

mocks.OnCall(mockRec, IRecord::at).With("firstName")
     .Return(std::ref(*cols[0]));
mocks.OnCall(mockRec, IRecord::at).With("lastName")
     .Return(std::ref(*cols[1]));

mocks.OnCall(cols[0], IColumn::getValue)
     .Return(std::string("John"));
mocks.OnCall(cols[1], IColumn::getValue)
     .Return(std::string("Doe"));

which will work in any order of the calls. Alternatively you can also use Do

std::map<std::string, IColumn*> map;

map["firstName"] = mocks.Mock<IColumn>();
map["lastName"] = mocks.Mock<IColumn>();

mocks.OnCall(mockRec, IRecord::at)
     .Do( [&map]( std::string& key){ return std::ref(*map[key]); } );

mocks.OnCall(map["firstName"], IColumn::getValue)
     .Return(std::string("John"));
mocks.OnCall(map["lastName"], IColumn::getValue)
     .Return(std::string("Doe"));
Thomas
  • 4,980
  • 2
  • 15
  • 30
  • I realized yesterday that I was using the version from SVN and not Git which apparently isn't as up-to-date. I haven't had a chance to retry this; I'll update when I try on the latest version. – Bret Kuhns Mar 09 '13 at 17:49
  • wow, that's an awesome solution; definitely flexible! I like the `Do` version the best because it'll reduce redundant expectation lines. I wouldn't have thought of doing it that way. Thanks! – Bret Kuhns Mar 09 '13 at 20:54
  • the autoExpect is to give a default order, which will make your test fail earlier. Disabling it is the correct way of using mocks, but I expect most to have underspecified order then, which will lead to odd matching order as well (as you may match another call first). It's the safe and typically wrong choice... but it's safe. – dascandy Apr 10 '13 at 12:35