Skip to content

🎭 GMock — The Art of Mocking Advanced

Mocking — Kỹ thuật thay thế dependencies thực bằng fake objects để kiểm soát hoàn toàn môi trường test.

Tại sao cần Mocking?

┌─────────────────────────────────────────────────────────────────────────┐
│                    THE MOCKING PROBLEM                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Bạn muốn test PaymentService:                                         │
│                                                                         │
│   ┌─────────────────┐     ┌─────────────────┐                          │
│   │ PaymentService  │────►│  Real Database  │ ← Cần setup PostgreSQL   │
│   │                 │────►│  Stripe API     │ ← Charges REAL money!    │
│   │                 │────►│  Email Service  │ ← Sends REAL emails!     │
│   └─────────────────┘     └─────────────────┘                          │
│                                                                         │
│   Problems:                                                             │
│   • Need real DB running (slow, complex setup)                          │
│   • Each test charges real money ($$$$)                                 │
│   • Sends spam to real customers                                        │
│   • Network failures cause flaky tests                                  │
│   • Cannot test error scenarios (DB down, API timeout)                  │
│                                                                         │
│   Solution: MOCK the dependencies!                                      │
│                                                                         │
│   ┌─────────────────┐     ┌─────────────────┐                          │
│   │ PaymentService  │────►│  MockDatabase   │ ← In-memory, controllable│
│   │  (same code!)   │────►│  MockStripe     │ ← No real charges        │
│   │                 │────►│  MockEmail      │ ← No real emails         │
│   └─────────────────┘     └─────────────────┘                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Dependency Injection — The Foundation (@[/refactor])

Bạn KHÔNG THỂ mock code mà không dùng Dependency Injection!

Step 1: Define Interface

cpp
// interfaces.hpp
#pragma once
#include <string>
#include <optional>

class IDatabase {
public:
    virtual ~IDatabase() = default;
    virtual std::optional<double> GetBalance(int user_id) = 0;
    virtual bool UpdateBalance(int user_id, double amount) = 0;
};

class IPaymentGateway {
public:
    virtual ~IPaymentGateway() = default;
    virtual bool Charge(const std::string& token, double amount) = 0;
    virtual bool Refund(const std::string& transaction_id) = 0;
};

class IEmailService {
public:
    virtual ~IEmailService() = default;
    virtual bool Send(const std::string& to, 
                      const std::string& subject,
                      const std::string& body) = 0;
};

Step 2: Refactor Service to Use Interfaces

cpp
// payment_service.hpp
#pragma once
#include "interfaces.hpp"
#include <memory>

class PaymentService {
public:
    PaymentService(std::shared_ptr<IDatabase> db,
                   std::shared_ptr<IPaymentGateway> gateway,
                   std::shared_ptr<IEmailService> email)
        : db_(std::move(db))
        , gateway_(std::move(gateway))
        , email_(std::move(email)) {}
    
    enum class Result {
        Success,
        InsufficientFunds,
        PaymentFailed,
        DatabaseError
    };
    
    Result ProcessPayment(int user_id, 
                          const std::string& card_token,
                          double amount);

private:
    std::shared_ptr<IDatabase> db_;
    std::shared_ptr<IPaymentGateway> gateway_;
    std::shared_ptr<IEmailService> email_;
};
cpp
// payment_service.cpp
#include "payment_service.hpp"

PaymentService::Result PaymentService::ProcessPayment(
    int user_id, 
    const std::string& card_token,
    double amount) {
    
    // 1. Get current balance
    auto balance = db_->GetBalance(user_id);
    if (!balance.has_value()) {
        return Result::DatabaseError;
    }
    
    // 2. Check sufficient funds
    if (*balance < amount) {
        return Result::InsufficientFunds;
    }
    
    // 3. Charge payment
    if (!gateway_->Charge(card_token, amount)) {
        return Result::PaymentFailed;
    }
    
    // 4. Update balance
    db_->UpdateBalance(user_id, *balance - amount);
    
    // 5. Send confirmation email
    email_->Send("user@example.com", 
                 "Payment Confirmed", 
                 "Your payment of $" + std::to_string(amount));
    
    return Result::Success;
}

Step 3: Production Uses Real Implementations

cpp
// main.cpp (Production)
int main() {
    auto db = std::make_shared<PostgresDatabase>("prod.db.com:5432");
    auto gateway = std::make_shared<StripeGateway>("sk_live_xxx");
    auto email = std::make_shared<SendGridEmail>("api_key_xxx");
    
    PaymentService service(db, gateway, email);
    // Uses REAL dependencies
}

Creating Mock Classes

MOCK_METHOD Syntax

cpp
// mocks.hpp
#pragma once
#include <gmock/gmock.h>
#include "interfaces.hpp"

class MockDatabase : public IDatabase {
public:
    // MOCK_METHOD(return_type, method_name, (args), (specifiers));
    
    MOCK_METHOD(std::optional<double>, GetBalance, (int user_id), (override));
    MOCK_METHOD(bool, UpdateBalance, (int user_id, double amount), (override));
};

class MockPaymentGateway : public IPaymentGateway {
public:
    MOCK_METHOD(bool, Charge, (const std::string& token, double amount), (override));
    MOCK_METHOD(bool, Refund, (const std::string& transaction_id), (override));
};

class MockEmailService : public IEmailService {
public:
    MOCK_METHOD(bool, Send, 
                (const std::string& to, 
                 const std::string& subject,
                 const std::string& body), 
                (override));
};

EXPECT_CALL — The Heart of GMock

cpp
// payment_service_test.cpp
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "payment_service.hpp"
#include "mocks.hpp"

using ::testing::Return;
using ::testing::_;
using ::testing::Eq;

class PaymentServiceTest : public ::testing::Test {
protected:
    void SetUp() override {
        mock_db_ = std::make_shared<MockDatabase>();
        mock_gateway_ = std::make_shared<MockPaymentGateway>();
        mock_email_ = std::make_shared<MockEmailService>();
        
        service_ = std::make_unique<PaymentService>(
            mock_db_, mock_gateway_, mock_email_);
    }
    
    std::shared_ptr<MockDatabase> mock_db_;
    std::shared_ptr<MockPaymentGateway> mock_gateway_;
    std::shared_ptr<MockEmailService> mock_email_;
    std::unique_ptr<PaymentService> service_;
};

TEST_F(PaymentServiceTest, SuccessfulPayment) {
    // ARRANGE: Set up expectations
    EXPECT_CALL(*mock_db_, GetBalance(123))
        .WillOnce(Return(100.0));
    
    EXPECT_CALL(*mock_gateway_, Charge("tok_xxx", 50.0))
        .WillOnce(Return(true));
    
    EXPECT_CALL(*mock_db_, UpdateBalance(123, 50.0))
        .WillOnce(Return(true));
    
    EXPECT_CALL(*mock_email_, Send(_, _, _))
        .WillOnce(Return(true));
    
    // ACT
    auto result = service_->ProcessPayment(123, "tok_xxx", 50.0);
    
    // ASSERT
    EXPECT_EQ(result, PaymentService::Result::Success);
}

TEST_F(PaymentServiceTest, InsufficientFunds) {
    EXPECT_CALL(*mock_db_, GetBalance(123))
        .WillOnce(Return(30.0));  // Only $30
    
    // Gateway should NOT be called!
    EXPECT_CALL(*mock_gateway_, Charge(_, _)).Times(0);
    
    auto result = service_->ProcessPayment(123, "tok_xxx", 50.0);
    
    EXPECT_EQ(result, PaymentService::Result::InsufficientFunds);
}

TEST_F(PaymentServiceTest, PaymentGatewayFails) {
    EXPECT_CALL(*mock_db_, GetBalance(123))
        .WillOnce(Return(100.0));
    
    EXPECT_CALL(*mock_gateway_, Charge("tok_xxx", 50.0))
        .WillOnce(Return(false));  // Gateway fails!
    
    // Balance should NOT be updated
    EXPECT_CALL(*mock_db_, UpdateBalance(_, _)).Times(0);
    
    auto result = service_->ProcessPayment(123, "tok_xxx", 50.0);
    
    EXPECT_EQ(result, PaymentService::Result::PaymentFailed);
}

EXPECT_CALL Syntax Deep Dive

cpp
EXPECT_CALL(mock_object, method_name(matchers))
    .Times(cardinality)
    .WillOnce(action)
    .WillRepeatedly(action);

Matchers

cpp
using namespace ::testing;

// Exact value
EXPECT_CALL(mock, Method(123, "hello"));

// Any value
EXPECT_CALL(mock, Method(_, _));              // Any args
EXPECT_CALL(mock, Method(123, _));            // First must be 123

// Comparison matchers
EXPECT_CALL(mock, Method(Eq(123)));           // ==
EXPECT_CALL(mock, Method(Ne(0)));             // !=
EXPECT_CALL(mock, Method(Lt(100)));           // <
EXPECT_CALL(mock, Method(Le(100)));           // <=
EXPECT_CALL(mock, Method(Gt(0)));             // >
EXPECT_CALL(mock, Method(Ge(0)));             // >=

// String matchers
EXPECT_CALL(mock, Method(HasSubstr("hello"))); // Contains
EXPECT_CALL(mock, Method(StartsWith("pre")));  // Prefix
EXPECT_CALL(mock, Method(EndsWith("suf")));    // Suffix
EXPECT_CALL(mock, Method(MatchesRegex("^a.*z$")));

// Container matchers
EXPECT_CALL(mock, Method(IsEmpty()));
EXPECT_CALL(mock, Method(Contains(5)));
EXPECT_CALL(mock, Method(ElementsAre(1, 2, 3)));

Cardinality (Times)

cpp
EXPECT_CALL(mock, Method(_))
    .Times(1);              // Exactly once
    .Times(0);              // Never called
    .Times(AtLeast(2));     // >= 2 times
    .Times(AtMost(5));      // <= 5 times
    .Times(Between(2, 5));  // 2-5 times
    .Times(AnyNumber());    // Any number of times

Actions

cpp
using namespace ::testing;

// Return values
EXPECT_CALL(mock, GetValue())
    .WillOnce(Return(42));
    
EXPECT_CALL(mock, GetValue())
    .WillOnce(Return(1))
    .WillOnce(Return(2))
    .WillRepeatedly(Return(99));  // After first 2, always 99

// Throw exception
EXPECT_CALL(mock, DangerousMethod())
    .WillOnce(Throw(std::runtime_error("DB error")));

// Do nothing (for void methods)
EXPECT_CALL(mock, VoidMethod())
    .WillOnce(Return());

// Custom action
EXPECT_CALL(mock, Process(_))
    .WillOnce([](int x) { return x * 2; });

Verification: Strict vs Nice Mocks

cpp
// STRICT MOCK: Fails if ANY unexpected call happens
testing::StrictMock<MockDatabase> strict_db;
// If Method() is called without EXPECT_CALL → TEST FAILS

// NICE MOCK: Ignores unexpected calls (returns default)
testing::NiceMock<MockDatabase> nice_db;
// If Method() is called without EXPECT_CALL → Returns default value

// Default: Warns about unexpected calls but doesn't fail
MockDatabase default_db;
┌─────────────────────────────────────────────────────────────────────────┐
│                    MOCK STRICTNESS COMPARISON                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Type          Unexpected Call    Missing Call    Use Case             │
│   ────────────  ────────────────   ──────────────  ──────────────────   │
│   StrictMock    FAIL               FAIL            Strict verification  │
│   NiceMock      Ignore (default)   FAIL            Focus on key calls   │
│   Default       Warning            FAIL            Balanced             │
│                                                                         │
│   Recommendation:                                                       │
│   • StrictMock for critical business logic tests                        │
│   • NiceMock when testing subset of interactions                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Mocking Sequence (Order Matters)

cpp
TEST_F(PaymentTest, OperationsInOrder) {
    using ::testing::InSequence;
    
    InSequence seq;  // All EXPECT_CALLs after this must happen in order
    
    EXPECT_CALL(*mock_db_, GetBalance(123));     // 1st
    EXPECT_CALL(*mock_gateway_, Charge(_, _));   // 2nd
    EXPECT_CALL(*mock_db_, UpdateBalance(_, _)); // 3rd
    EXPECT_CALL(*mock_email_, Send(_, _, _));    // 4th
    
    service_->ProcessPayment(123, "tok", 50.0);
}

Best Practices

┌─────────────────────────────────────────────────────────────────────────┐
│                    GMOCK BEST PRACTICES                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ✅ DO                                                                  │
│   ─────                                                                 │
│   • Create interfaces for all external dependencies                     │
│   • Use constructor injection (not setter injection)                    │
│   • Set expectations BEFORE calling the method under test               │
│   • Use StrictMock for critical business logic                          │
│   • Document WHY each expectation matters                               │
│                                                                         │
│   ❌ DON'T                                                               │
│   ───────                                                               │
│   • Don't mock everything (only external dependencies)                  │
│   • Don't mock value objects or DTOs                                    │
│   • Don't use NiceMock to hide unexpected interactions                  │
│   • Don't over-specify (test behavior, not implementation)              │
│                                                                         │
│   ⚠️ WARNING                                                            │
│   ───────────                                                           │
│   Mocking private methods is a code smell!                              │
│   If you need to mock a private method → refactor to interface.         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Bước tiếp theo

🔬 Advanced Testing → — Parameterized Tests, Edge Cases, Code Coverage