如何用Gmock实现可手动控制阻塞状态的Mock函数?
Got it, let's tackle how to implement a blocking mock function with GMock for your scenario—this is a common use case when testing threaded code that relies on blocking I/O functions like your Read method. The goal is to make the mock Read block by default, and let you manually trigger it to return a value (either success or failure) whenever you need during testing.
Core Approach
GMock doesn't have built-in blocking functionality, but we can combine C++ synchronization primitives (mutex + condition variable) with GMock's custom actions (Invoke) to build this behavior. Here's a step-by-step solution tailored to your code:
Step 1: Enhance Your Mock Class with Sync Primitives
First, update your BlockFuncs mock to include variables that control blocking, store the return value, and hold the message to output:
#include <mutex> #include <condition_variable> class BlockFuncs: public BlockFuncsIf { public: MOCK_METHOD1(Write, bool(const Msg&)); MOCK_METHOD1(Read, bool(Msg* msg)); // Synchronization primitives for blocking control std::mutex mtx; std::condition_variable cv; bool allow_read = false; // Flag to unblock Read bool read_return_value = false;// Value Read will return Msg output_msg; // Message Read will write to the output pointer };
Step 2: Define a Blocking Custom Action for Read
Create a helper function that implements the blocking logic. This function will be bound to the mock Read method using GMock's Invoke:
// Custom action that makes Read block until we trigger it bool BlockingReadAction(BlockFuncs* mock, Msg* msg) { std::unique_lock<std::mutex> lock(mock->mtx); // Wait until we explicitly allow Read to return mock->cv.wait(lock, [mock]() { return mock->allow_read; }); // Copy the pre-configured message to the output pointer if (msg != nullptr) { *msg = mock->output_msg; } // Save the return value and reset the block flag for next calls bool result = mock->read_return_value; mock->allow_read = false; return result; }
Step 3: Set Up the Mock in Your Test Case
In your test, bind the custom action to the mock Read method, then control the blocking behavior by triggering the condition variable:
#include <thread> #include <gtest/gtest.h> #include <gmock/gmock.h> TEST(TestedClassTest, ManualControlOfBlockingRead) { BlockFuncs mock_block_funcs; TestedClass tested_object(&mock_block_funcs); // Bind the blocking action to the mock Read method ON_CALL(mock_block_funcs, Read(_)) .WillByDefault(testing::Invoke(&mock_block_funcs, BlockingReadAction)); // Start the thread that runs expectRead (which loops on Read) std::thread read_thread(&TestedClass::expectRead, &tested_object); // -------------------------- // First manual trigger: make Read return true (keep the loop running) // -------------------------- { std::lock_guard<std::mutex> lock(mock_block_funcs.mtx); mock_block_funcs.output_msg = Msg{/* Initialize your test message here */}; mock_block_funcs.read_return_value = true; mock_block_funcs.allow_read = true; } mock_block_funcs.cv.notify_one(); // Wake up the blocked Read call // Wait for the TestedClass to process the Read (using its condition variable) std::unique_lock<std::mutex> tested_lock(tested_object.mtx); // Assume TestedClass has a mutex for its cv tested_object.cv.wait(tested_lock); // Wait until expectRead calls cv.notify_all() // -------------------------- // Second manual trigger: make Read return false (exit the loop) // -------------------------- { std::lock_guard<std::mutex> lock(mock_block_funcs.mtx); mock_block_funcs.read_return_value = false; mock_block_funcs.allow_read = true; } mock_block_funcs.cv.notify_one(); // Wait for the read thread to finish read_thread.join(); // Verify expectations (e.g., Read was called exactly twice) EXPECT_CALL(mock_block_funcs, Read(_)) .Times(2); }
Step 4: (Optional) Simplify Triggering with a Helper Function
To clean up your test code, wrap the trigger logic in a helper function:
void TriggerMockRead(BlockFuncs& mock, bool return_value, const Msg& msg = {}) { std::lock_guard<std::mutex> lock(mock.mtx); mock.output_msg = msg; mock.read_return_value = return_value; mock.allow_read = true; mock.cv.notify_one(); }
Then in your test, you can replace the trigger blocks with:
// Trigger Read to return true with a test message TriggerMockRead(mock_block_funcs, true, test_msg); tested_object.cv.wait(tested_lock); // Trigger Read to return false and exit the loop TriggerMockRead(mock_block_funcs, false);
Key Notes
- Avoid Deadlocks: Always ensure you lock the mutex before modifying the control flags (
allow_read,read_return_value), and never hold the mutex while waiting on a condition variable (usestd::unique_lockfor that). - Reset Flags: We reset
allow_readtofalseafter each trigger so subsequentReadcalls will block again automatically. - Sync with TestedClass: Use the
TestedClass's condition variable to wait for the Read operation to complete, ensuring your test steps execute in the correct order.
内容的提问来源于stack exchange,提问作者lior.i

