Giao diện
🎭 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 timesActions
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. │
│ │
└─────────────────────────────────────────────────────────────────────────┘