trainings/CreatingReliableSoftwareCpp/Presentation/testing_gmock.md

20 KiB
Raw Blame History

GMOCK

  • Avoid depricated MOCK_METHDn and MOCK_CONST_METHODn
  • Use MOCK_METHOD()
  • Use Times when want to expect how many times function was call (by default it expect once)
  • Use AtLeast To expect n or more calls
  • Use WillOnce and WillRepeatedly to perform some actions
  • Use SaveArg to save argument provided to mock function
  • Use InSequence to perform sequenced actions
  • You can mock non-virtual function
  • Use StrictMock when you need to be sure about every call of this mock
  • Use NiceMock when you don't care about function called for this mock
  • Use ON_CALL and WillByDefault to perform some default action, when function will be called
  • Use DoAll to perform more then one action
  • Use Invoke to invoke funtion/lambda

MOCK_METHOD

MOCK_METHOD(return_type, name_of_function, arguments, parameters)

class MockTurtle : public Turtle {
 public:
  ...
  MOCK_METHOD(void, PenUp, (), (override));
  MOCK_METHOD(void, PenDown, (), (override));
  MOCK_METHOD(void, Forward, (int distance), (override));
  MOCK_METHOD(void, Turn, (int degrees), (override));
  MOCK_METHOD(void, GoTo, (int x, int y), (override));
  MOCK_METHOD(int, GetX, (), (const, override));
  MOCK_METHOD(int, GetY, (), (const, override));
};
  • const - Makes the mocked method a const method. Required if overriding a const method.
  • override - Marks the method with override. Recommended if overriding a virtual method.
  • noexcept - Marks the method with noexcept. Required if overriding a noexcept method.
  • Calltype(...) - Sets the call type for the method (e.g. to STDMETHODCALLTYPE), useful in Windows.
  • ref(...) - Marks the method with the reference qualification specified. Required if overriding a method that has reference qualifications. Eg ref(&) or ref(&&).

Test class

Let's use once agin Foo and Bar class

class Bar {
public:
    virtual ~Bar() = default;

    virtual bool doOtherStuff(const std::string& str, int*, const std::vector<int>& vec) const {
        // do some stuff
        return false;
    }
};

class MockBar : public Bar {
public:
    ~MockBar() override = default;
    MOCK_METHOD(bool, doOtherStuff, (const std::string&, int*, const std::vector<int>&), (const override));
};

class Foo {
public:
    Foo(std::unique_ptr<Bar> bar)
        : val_(std::make_unique<int>(42)),
          bar_(std::move(bar)) {}

    bool doSth(const std::string& str) {
        return bar_->doOtherStuff(str, val_.get(), {1, 2, 3, 4});
    }

private:
    std::unique_ptr<int> val_;
    std::unique_ptr<Bar> bar_;
};


Times

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    EXPECT_CALL(*mockBarPtr, doOtherStuff).Times(2);

    foo.doSth("Sth1");
    foo.doSth("Sth2");
}

AtLeast

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    EXPECT_CALL(*mockBarPtr, doOtherStuff).Times(testing::AtLeast(2));

    foo.doSth("Sth1");
    foo.doSth("Sth2");
}

WillOnce and WillRepeatedly

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    // Dont do that. Next exceptaion will override first one!
    // EXPECT_CALL(*mockBarPtr, doOtherStuff).WillOnce(Return(false));
    // EXPECT_CALL(*mockBarPtr, doOtherStuff).WillOnce(Return(true));

    // Correct one!
    EXPECT_CALL(*mockBarPtr, doOtherStuff)
        .WillOnce(Return(false))
        .WillOnce(Return(true));

    EXPECT_FALSE(foo.doSth("Sth1"));
    EXPECT_TRUE(foo.doSth("Sth2"));
}
TEST(ExampleTest, ShouldTest) {
    //...
    EXPECT_CALL(*mockBarPtr, doOtherStuff)
        .WillOnce(Return(false))
        .WillOnce(Return(true))
        .WillRepeatedly(Return(false));

    EXPECT_FALSE(foo.doSth("Sth1"));
    EXPECT_TRUE(foo.doSth("Sth2"));
    EXPECT_FALSE(foo.doSth("Sth3"));
    EXPECT_FALSE(foo.doSth("Sth4"));
}

Save arguments provided to functions (1)

We want to capture parameters provided to bar function.

class Bar {
public:
    virtual ~Bar() = default;

    virtual bool doOtherStuff(const std::string& str, int val, const std::vector<int>& vec) {
        // do some stuff
        return false;
    }
};

class MockBar : public Bar {
public:
    ~MockBar() override = default;
    MOCK_METHOD(bool, doOtherStuff, (const std::string&, int, const std::vector<int>&), (override));
};

class Foo {
public:
    Foo(std::unique_ptr<Bar> bar)
        : bar_(std::move(bar)) {}

    bool doSth(const std::string& str) {
        return bar_->doOtherStuff(str, 42, {1, 2, 3, 4});
    }

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

Save arguments provided to functions (2)

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    int val;
    std::vector<int> vec;
    // Also return true, instead of false like in original function
    EXPECT_CALL(*mockBarPtr, doOtherStuff).
        WillOnce(DoAll(SaveArg<1>(&val), SaveArg<2>(&vec), Return(true)));

    EXPECT_TRUE(foo.doSth("Sth"));
    EXPECT_EQ(val, 42);
    const auto expected = std::vector<int>{1, 2, 3, 4};
    EXPECT_EQ(vec, expected);
}
[----------] 1 test from ExampleTest
[ RUN      ] ExampleTest.ShouldTest
[       OK ] ExampleTest.ShouldTest (0 ms)
[----------] 1 test from ExampleTest (0 ms total)

Save pointer provided to function

class Bar {
public:
    virtual ~Bar() = default;

    virtual bool doOtherStuff(const std::string& str, int* val, const std::vector<int>& vec) {
        // do some stuff
        return false;
    }
};

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    int* val;
    std::vector<int> vec;
    // Capture by SaveArgPointee
    EXPECT_CALL(*mockBarPtr, doOtherStuff).
        WillOnce(DoAll(SaveArgPointee<1>(val), SaveArg<2>(&vec), Return(true)));

    EXPECT_TRUE(foo.doSth("Sth"));
    EXPECT_EQ(*val, 42);
    const auto expected = std::vector<int>{1, 2, 3, 4};
    EXPECT_EQ(vec, expected);
}

Capture non-copyable object

  • There is a problem to capture non-copyable objects because we can't copy them!
  • We need to make an ugly hack to capture this object -> move this object to our variable.
  • This will be problematic when this object needs to be used in a function, but it will work when we used in on mock function because the argument will not be used later.
// Bar
bool doSth(const std::string& str) {
    return bar_->doOtherStuff(str, std::make_unique<int>(42), {1, 2, 3, 4});
}

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    std::unique_ptr<int> val;
    EXPECT_CALL(*mockBarPtr, doOtherStuff).WillOnce(DoAll(MoveObject<1>(&val), Return(true)));

    EXPECT_TRUE(foo.doSth("Sth"));
    EXPECT_EQ(*val, 42);
}
  • There is no such function in gtest, so we need to implement somehow MoveObject

Move object

ACTION_TEMPLATE(MoveObject,
                HAS_1_TEMPLATE_PARAMS(int, k),
                AND_1_VALUE_PARAMS(pointer)) {
    using PtrType = std::remove_cv_t<std::remove_reference_t<decltype(std::get<k>(args))>>;
    // This unique_ptr will be deleted after function end
    // so we need to capture it
    *pointer = std::move(const_cast<PtrType&>(std::get<k>(args)));
}

When we only need a raw pointer from unique_ptr

ACTION_TEMPLATE(ExtractPtr,
                HAS_1_TEMPLATE_PARAMS(int, k),
                AND_1_VALUE_PARAMS(pointer)) {
    *pointer = std::get<k>(args).get();
}

But we will lost it, because unique_ptr will be deleted after exit form mock function!

This is a very old way, the better one is to just lambda instead, I will show this later


InSequence (1)

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    testing::Sequence seq;
    // Four expectations in sequence. There is no override like before
    for (int i = 0; i < 4; ++i) {
        EXPECT_CALL(*mockBarPtr, doOtherStuff)
            .InSequence(seq)
            .WillOnce(Return(i % 2));
    }

    EXPECT_FALSE(foo.doSth("Sth1"));
    EXPECT_TRUE(foo.doSth("Sth2"));
    EXPECT_FALSE(foo.doSth("Sth3"));
    EXPECT_TRUE(foo.doSth("Sth4"));
}

InSequence (2)

This has the same behaviour like previous example

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    {
        testing::InSequence seq;
        // Four expectations in sequence. There is no override like before
        for (int i = 0; i < 4; ++i) {
            EXPECT_CALL(*mockBarPtr, doOtherStuff).WillOnce(Return(i % 2));
        }
    }

    EXPECT_FALSE(foo.doSth("Sth1"));
    EXPECT_TRUE(foo.doSth("Sth2"));
    EXPECT_FALSE(foo.doSth("Sth3"));
    EXPECT_TRUE(foo.doSth("Sth4"));
}

InSequence - branch out

testing::Sequence s1, s2;

EXPECT_CALL(foo, Method1())
    .InSequence(s1, s2);
EXPECT_CALL(bar, Method2())
    .InSequence(s1);
EXPECT_CALL(bar, Method3())
    .InSequence(s2);
EXPECT_CALL(foo, Method4())
    .InSequence(s2);
             +---> Method2 (seq1)
(seq1 seq2)  |
  Method1----|
             |
             +---> Method3 ---> Method4 (seq2)

mock non-virtual function (1)

We have the following class

struct Packet {};

class ConcretePacketStream {
public:
    void AppendPacket(Packet* new_packet) {}
    const Packet* GetPacket(size_t packet_number) const {}
    size_t NumberOfPackets() const {}
};

mock non-virtual function (2)

We can create mock and instead of dynamic polymorphism use static polymorphism (templates)

// A mock packet stream class. It inherits from no other, but defines
// GetPacket() and NumberOfPackets(). No need to add all functions like: AppendPacket
class MockPacketStream {
public:
    // Do not add override!
    MOCK_METHOD(const Packet*, GetPacket, (size_t packet_number), (const));
    MOCK_METHOD(size_t, NumberOfPackets, (), (const));
};

// Now we can use static polymorphism to insert Mock class in a test,
// and real class in production code
template <class PacketStream>
class PacketReader {
public:
    const Packet* ReadPackets(PacketStream* stream, size_t packet_num) {
        return stream->GetPacket(packet_num);
    }
};

mock non-virtual function (3)

Now in test we can use mock class

TEST(TestNonVirtualMethod, ShouldTest) {
    MockPacketStream mock_stream;
    EXPECT_CALL(mock_stream, GetPacket).WillOnce(Return(nullptr));

    PacketReader<MockPacketStream> reader;
    reader.ReadPackets(&mock_stream, 12);
}

And in production code the real one

ConcretePacketStream stream;
PacketReader<ConcretePacketStream> reader;

auto* packet = reader.ReadPackets(&stream, 12);

StrictMock

The usage of StrictMock is similar to normal mock, except that it makes all uninteresting calls failures:

class MockFoo : public Foo {
    //...
    MOCK_METHOD(void, Fun1, (), (override));
    MOCK_METHOD(void, Fun2, (), (override));
};

TEST(SimpleTest, TestSth) {
  StrictMock<MockFoo> mock_foo;
  // When Fun2 will be also called it cause test failed
  EXPECT_CALL(mock_foo, Fun1());
}

NiceMock

  • The usage of `StrictMock` is similar to normal mock, except that it reject uninteresting calls (we will not see them while run test)
  • NiceMock and StrictMock only affects uninteresting calls (calls of methods with no expectations); they do not affect unexpected calls (calls of methods with expectations, but they dont match)
class MockFoo : public Foo {
    //...
    MOCK_METHOD(void, Fun1, (), (override));
    MOCK_METHOD(void, Fun2, (), (override));
};

TEST(SimpleTest, TestSth) {
  NiceMock<MockFoo> mock_foo;
  // When Fun2 will be also called it will be ignored
  EXPECT_CALL(mock_foo, Fun1());
}

ON_CALL and WillByDefault

We can do whatever we want when some mock method will be called. We can even save all types of arguments, or move objects to test them (call later).

When a function takes for instance non-copyable callback, and we need to capture it to continue testing, we can move it inside lambda! This is much better than the tricky template shown before.

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    // Nice way to capture all types of argument
    int* ptr;
    ON_CALL(*mockBarPtr, doOtherStuff)
        .WillByDefault([&ptr](const std::string&, int* new_ptr, const std::vector<int>&) {
            ptr = new_ptr;
            return true;
        });

    EXPECT_CALL(*mockBarPtr, doOtherStuff);
    EXPECT_TRUE(foo.doSth("Sth1"));
    EXPECT_EQ(*ptr, 42);
}

DoAll

If we need to combine a few actions, we can do this inside doAll(...)

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));

    std::string str;
    int* ptr;

    EXPECT_CALL(*mockBarPtr, doOtherStuff)
        .WillOnce(DoAll(
            SaveArg<0>(&str),
            SetArgPointee<1>(80),   // Replace pointer value
            SaveArgPointee<1>(ptr), // Capture value of arg1
            Return(true)));

    EXPECT_TRUE(foo.doSth("Sth1"));
    EXPECT_EQ(str, "Sth1");
    EXPECT_EQ(*ptr, 80);
}

Invoke (1)

Let's change a little MockBar

class MockBar : public Bar {
public:
    ~MockBar() override = default;
    MOCK_METHOD(bool, doOtherStuff, (const std::string&, int*, std::function<void(int)>), (const override));
};

Now we want to run callback. We can do this without capture it!

EXPECT_CALL(*mockBarPtr, doOtherStuff)
    .WillOnce(DoAll(
        SaveArg<0>(&str),
        SetArgPointee<1>(80),    // Replace pointer value
        SaveArgPointee<1>(ptr),  // Capture value of arg1
        InvokeArgument<2>(500),  // Invoke callback
        Return(true)));

Invoke (2)

class Helper {
public:
    void validateArguments(const std::string& str, int* ptr, std::function<void(int)> callback) {
        ASSERT_EQ(str.size(), 4);
        EXPECT_EQ(str, "Sth1");
        ASSERT_TRUE(ptr);
        EXPECT_EQ(*ptr, 42);
        ASSERT_TRUE(callback);
        callback(50);  // We can invoke callback here
    }
};

TEST(ExampleTest, ShouldTest) {
    auto mockBar = std::make_unique<MockBar>();
    auto* mockBarPtr = mockBar.get();
    Foo foo(std::move(mockBar));
    Helper helper;

    EXPECT_CALL(*mockBarPtr, doOtherStuff)
        .WillOnce(DoAll(
            Invoke(&helper, &Helper::validateArguments),
            Return(true)));

    EXPECT_TRUE(foo.doSth("Sth1"));
}

Exercise 2

Open project SHM and write test for class Store.cpp which will test it's interface. You need to mock Cargo class and check if you get correct result based on what you mock. Write mock in Presentation/exercises/SHM/tests/CargoMock.h for Cargo class.

Go to Presentation/exercises/SHM/build/test and run test using command ./SHM_Tests.