[bt][l2cap] Improve FakeSignalingChannel outbound commands

Add file and line number to outbound request expectactions. Remove the
need to call std::make_pair for simulated responses.

Add ReceiveResponses to asynchronously trigger simulated responses to an
outbound command.

Fail tests that send fewer or more request commands than expected and
tests that don't consume all the simulated responses.

BT-691

Test: bt-host-unittests
Changes to L2CAP_BrEdrCommandHandlerTest and
L2CAP_BrEdrDynamicChannelTest

Change-Id: I5a0eef571f3512290a9934768b114b97dac42432
diff --git a/garnet/drivers/bluetooth/lib/l2cap/bredr_command_handler_unittest.cc b/garnet/drivers/bluetooth/lib/l2cap/bredr_command_handler_unittest.cc
index 6399c9e..286a298 100644
--- a/garnet/drivers/bluetooth/lib/l2cap/bredr_command_handler_unittest.cc
+++ b/garnet/drivers/bluetooth/lib/l2cap/bredr_command_handler_unittest.cc
@@ -78,9 +78,8 @@
 
       // Remote (relative to rejecter) CID
       LowerBits(kBadLocalCId), UpperBits(kBadLocalCId));
-  fake_sig()->AddOutbound(
-      kConnectionRequest, expected_conn_req.view(),
-      std::make_pair(SignalingChannel::Status::kReject, rej_rsp.view()));
+  EXPECT_OUTBOUND_REQ(*fake_sig(), kConnectionRequest, expected_conn_req.view(),
+                      {SignalingChannel::Status::kReject, rej_rsp.view()});
 
   bool cb_called = false;
   auto on_conn_rsp = [&cb_called, kBadLocalCId](
@@ -120,9 +119,8 @@
 
       // Status (No further information available)
       0x00, 0x00);
-  fake_sig()->AddOutbound(
-      kConnectionRequest, expected_conn_req.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, ok_conn_rsp.view()));
+  EXPECT_OUTBOUND_REQ(*fake_sig(), kConnectionRequest, expected_conn_req.view(),
+                      {SignalingChannel::Status::kSuccess, ok_conn_rsp.view()});
 
   bool cb_called = false;
   auto on_conn_rsp =
@@ -177,10 +175,10 @@
 
       // Status (No further information available)
       0x00, 0x00);
-  fake_sig()->AddOutbound(
-      kConnectionRequest, expected_conn_req.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, pend_conn_rsp.view()),
-      std::make_pair(SignalingChannel::Status::kSuccess, ok_conn_rsp.view()));
+  EXPECT_OUTBOUND_REQ(
+      *fake_sig(), kConnectionRequest, expected_conn_req.view(),
+      {SignalingChannel::Status::kSuccess, pend_conn_rsp.view()},
+      {SignalingChannel::Status::kSuccess, ok_conn_rsp.view()});
 
   int cb_count = 0;
   auto on_conn_rsp =
@@ -375,9 +373,9 @@
       'l', 'o', 'l', 'z');
   const BufferView& rsp_options = pending_config_req.view(6, 4);
 
-  fake_sig()->AddOutbound(kConfigurationRequest, expected_config_req.view(),
-                          std::make_pair(SignalingChannel::Status::kSuccess,
-                                         pending_config_req.view()));
+  EXPECT_OUTBOUND_REQ(
+      *fake_sig(), kConfigurationRequest, expected_config_req.view(),
+      {SignalingChannel::Status::kSuccess, pending_config_req.view()});
 
   bool cb_called = false;
   BrEdrCommandHandler::ConfigurationResponseCallback on_config_rsp =
@@ -412,9 +410,9 @@
   // the response's payload should be the same as the request's
   const ByteBuffer& ok_discon_rsp = expected_discon_req;
 
-  fake_sig()->AddOutbound(
-      kDisconnectionRequest, expected_discon_req.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, ok_discon_rsp.view()));
+  EXPECT_OUTBOUND_REQ(
+      *fake_sig(), kDisconnectionRequest, expected_discon_req.view(),
+      {SignalingChannel::Status::kSuccess, ok_discon_rsp.view()});
 
   bool cb_called = false;
   BrEdrCommandHandler::DisconnectionResponseCallback on_discon_req =
@@ -453,9 +451,9 @@
       // Destination CID (relative to rejecter)
       LowerBits(kLocalCId), UpperBits(kLocalCId));
 
-  fake_sig()->AddOutbound(
-      kDisconnectionRequest, expected_discon_req.view(),
-      std::make_pair(SignalingChannel::Status::kReject, rej_cid.view()));
+  EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest,
+                      expected_discon_req.view(),
+                      {SignalingChannel::Status::kReject, rej_cid.view()});
 
   bool cb_called = false;
   BrEdrCommandHandler::DisconnectionResponseCallback on_discon_cb =
diff --git a/garnet/drivers/bluetooth/lib/l2cap/bredr_dynamic_channel_unittest.cc b/garnet/drivers/bluetooth/lib/l2cap/bredr_dynamic_channel_unittest.cc
index db89319..9047278 100644
--- a/garnet/drivers/bluetooth/lib/l2cap/bredr_dynamic_channel_unittest.cc
+++ b/garnet/drivers/bluetooth/lib/l2cap/bredr_dynamic_channel_unittest.cc
@@ -326,9 +326,9 @@
 };
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, FailConnectChannel) {
-  sig()->AddOutbound(kConnectionRequest, kConnReq.view(),
-                     std::make_pair(SignalingChannel::Status::kSuccess,
-                                    kRejectConnRsp.view()));
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConnectionRequest, kConnReq.view(),
+      {SignalingChannel::Status::kSuccess, kRejectConnRsp.view()});
 
   // Build channel and operate it directly to be able to inspect it in the
   // connected but not open state.
@@ -366,12 +366,11 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, ConnectChannelFailConfig) {
-  sig()->AddOutbound(
-      kConnectionRequest, kConnReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConnRsp.view()));
-  sig()->AddOutbound(kConfigurationRequest, kConfigReq.view(),
-                     std::make_pair(SignalingChannel::Status::kReject,
-                                    kRejNotUnderstood.view()));
+  EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(),
+                      {SignalingChannel::Status::kSuccess, kOkConnRsp.view()});
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kReject, kRejNotUnderstood.view()});
 
   // Build channel and operate it directly to be able to inspect it in the
   // connected but not open state.
@@ -410,9 +409,9 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, ConnectChannelFailInvalidResponse) {
-  sig()->AddOutbound(kConnectionRequest, kConnReq.view(),
-                     std::make_pair(SignalingChannel::Status::kSuccess,
-                                    kInvalidConnRsp.view()));
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConnectionRequest, kConnReq.view(),
+      {SignalingChannel::Status::kSuccess, kInvalidConnRsp.view()});
 
   // Build channel and operate it directly to be able to inspect it in the
   // connected but not open state.
@@ -442,15 +441,13 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, OpenAndLocalCloseChannel) {
-  sig()->AddOutbound(
-      kConnectionRequest, kConnReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConnRsp.view()));
-  sig()->AddOutbound(
-      kConfigurationRequest, kConfigReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConfigRsp.view()));
-  sig()->AddOutbound(
-      kDisconnectionRequest, kDisconReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kDisconRsp.view()));
+  EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(),
+                      {SignalingChannel::Status::kSuccess, kOkConnRsp.view()});
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kSuccess, kOkConfigRsp.view()});
+  EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(),
+                      {SignalingChannel::Status::kSuccess, kDisconRsp.view()});
 
   int open_cb_count = 0;
   auto open_cb = [&open_cb_count](auto chan) {
@@ -497,12 +494,11 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, OpenAndRemoteCloseChannel) {
-  sig()->AddOutbound(
-      kConnectionRequest, kConnReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConnRsp.view()));
-  sig()->AddOutbound(
-      kConfigurationRequest, kConfigReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConfigRsp.view()));
+  EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(),
+                      {SignalingChannel::Status::kSuccess, kOkConnRsp.view()});
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kSuccess, kOkConfigRsp.view()});
 
   int open_cb_count = 0;
   auto open_cb = [&open_cb_count](auto chan) { open_cb_count++; };
@@ -537,17 +533,15 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, OpenChannelWithPendingConn) {
-  sig()->AddOutbound(
-      kConnectionRequest, kConnReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess,
-                     kPendingConnRsp.view()),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConnRsp.view()));
-  sig()->AddOutbound(
-      kConfigurationRequest, kConfigReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConfigRsp.view()));
-  sig()->AddOutbound(
-      kDisconnectionRequest, kDisconReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kDisconRsp.view()));
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConnectionRequest, kConnReq.view(),
+      {SignalingChannel::Status::kSuccess, kPendingConnRsp.view()},
+      {SignalingChannel::Status::kSuccess, kOkConnRsp.view()});
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kSuccess, kOkConfigRsp.view()});
+  EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(),
+                      {SignalingChannel::Status::kSuccess, kDisconRsp.view()});
 
   int open_cb_count = 0;
   registry()->OpenOutbound(kPsm, [&open_cb_count](auto chan) {
@@ -568,17 +562,15 @@
 TEST_F(L2CAP_BrEdrDynamicChannelTest, OpenChannelMismatchConnRsp) {
   // The first Connection Response (pending) has a different ID than the final
   // Connection Response (success).
-  sig()->AddOutbound(
-      kConnectionRequest, kConnReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess,
-                     kPendingConnRspWithId.view()),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConnRsp.view()));
-  sig()->AddOutbound(
-      kConfigurationRequest, kConfigReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConfigRsp.view()));
-  sig()->AddOutbound(
-      kDisconnectionRequest, kDisconReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kDisconRsp.view()));
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConnectionRequest, kConnReq.view(),
+      {SignalingChannel::Status::kSuccess, kPendingConnRspWithId.view()},
+      {SignalingChannel::Status::kSuccess, kOkConnRsp.view()});
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kSuccess, kOkConfigRsp.view()});
+  EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(),
+                      {SignalingChannel::Status::kSuccess, kDisconRsp.view()});
 
   int open_cb_count = 0;
   registry()->OpenOutbound(kPsm, [&open_cb_count](auto chan) {
@@ -597,17 +589,14 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, OpenChannelConfigPending) {
-  sig()->AddOutbound(
-      kConnectionRequest, kConnReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConnRsp.view()));
-  sig()->AddOutbound(
-      kConfigurationRequest, kConfigReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess,
-                     kPendingConfigRsp.view()),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConfigRsp.view()));
-  sig()->AddOutbound(
-      kDisconnectionRequest, kDisconReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kDisconRsp.view()));
+  EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(),
+                      {SignalingChannel::Status::kSuccess, kOkConnRsp.view()});
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kSuccess, kPendingConfigRsp.view()},
+      {SignalingChannel::Status::kSuccess, kOkConfigRsp.view()});
+  EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(),
+                      {SignalingChannel::Status::kSuccess, kDisconRsp.view()});
 
   int open_cb_count = 0;
   registry()->OpenOutbound(kPsm, [&open_cb_count](auto chan) {
@@ -626,15 +615,13 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, OpenChannelConfigWrongId) {
-  sig()->AddOutbound(
-      kConnectionRequest, kConnReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConnRsp.view()));
-  sig()->AddOutbound(kConfigurationRequest, kConfigReq.view(),
-                     std::make_pair(SignalingChannel::Status::kSuccess,
-                                    kUnknownIdConfigRsp.view()));
-  sig()->AddOutbound(
-      kDisconnectionRequest, kDisconReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kDisconRsp.view()));
+  EXPECT_OUTBOUND_REQ(*sig(), kConnectionRequest, kConnReq.view(),
+                      {SignalingChannel::Status::kSuccess, kOkConnRsp.view()});
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kSuccess, kUnknownIdConfigRsp.view()});
+  EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(),
+                      {SignalingChannel::Status::kSuccess, kDisconRsp.view()});
 
   int open_cb_count = 0;
   registry()->OpenOutbound(kPsm, [&open_cb_count](auto chan) {
@@ -651,12 +638,11 @@
 }
 
 TEST_F(L2CAP_BrEdrDynamicChannelTest, InboundConnectionOk) {
-  sig()->AddOutbound(
-      kConfigurationRequest, kConfigReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kOkConfigRsp.view()));
-  sig()->AddOutbound(
-      kDisconnectionRequest, kDisconReq.view(),
-      std::make_pair(SignalingChannel::Status::kSuccess, kDisconRsp.view()));
+  EXPECT_OUTBOUND_REQ(
+      *sig(), kConfigurationRequest, kConfigReq.view(),
+      {SignalingChannel::Status::kSuccess, kOkConfigRsp.view()});
+  EXPECT_OUTBOUND_REQ(*sig(), kDisconnectionRequest, kDisconReq.view(),
+                      {SignalingChannel::Status::kSuccess, kDisconRsp.view()});
 
   int open_cb_count = 0;
   DynamicChannelCallback open_cb = [&open_cb_count](auto chan) {
diff --git a/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.cc b/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.cc
index 5b51880..bac434f 100644
--- a/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.cc
+++ b/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.cc
@@ -5,7 +5,7 @@
 #include "garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.h"
 
 #include "garnet/drivers/bluetooth/lib/common/test_helpers.h"
-#include "lib/gtest/test_loop_fixture.h"
+#include "gtest/gtest.h"
 
 namespace btlib {
 namespace l2cap {
@@ -20,16 +20,16 @@
 class Expecter : public SignalingChannel::Responder {
  public:
   void Send(const common::ByteBuffer& rsp_payload) override {
-    FAIL() << "Unexpected local response " << rsp_payload.AsString();
+    ADD_FAILURE() << "Unexpected local response " << rsp_payload.AsString();
   }
 
   void RejectNotUnderstood() override {
-    FAIL() << "Unexpected local rejection, \"Not Understood\"";
+    ADD_FAILURE() << "Unexpected local rejection, \"Not Understood\"";
   }
 
   void RejectInvalidChannelId(ChannelId local_cid,
                               ChannelId remote_cid) override {
-    FAIL() << fxl::StringPrintf(
+    ADD_FAILURE() << fxl::StringPrintf(
         "Unexpected local rejection, \"Invalid Channel ID\" local: %#.4x "
         "remote: %#.4x",
         local_cid, remote_cid);
@@ -87,40 +87,77 @@
   ZX_DEBUG_ASSERT(dispatcher_);
 }
 
+FakeSignalingChannel::~FakeSignalingChannel() {
+  // Add a test failure for each expected request that wasn't received
+  for (size_t i = expected_transaction_index_; i < transactions_.size(); i++) {
+    ADD_FAILURE_AT(transactions_[i].file, transactions_[i].line)
+        << "Outbound request [" << i << "] expected "
+        << transactions_[i].responses.size() << " responses";
+  }
+}
+
 bool FakeSignalingChannel::SendRequest(CommandCode req_code,
                                        const common::ByteBuffer& payload,
                                        SignalingChannel::ResponseHandler cb) {
   if (expected_transaction_index_ >= transactions_.size()) {
+    ADD_FAILURE() << "Received unexpected outbound command after handling "
+                  << transactions_.size();
     return false;
   }
 
-  const Transaction& transaction = transactions_[expected_transaction_index_];
+  Transaction& transaction = transactions_[expected_transaction_index_];
+  ::testing::ScopedTrace trace(transaction.file, transaction.line,
+                               "Outbound request expected here");
   EXPECT_EQ(transaction.request_code, req_code);
   EXPECT_TRUE(common::ContainersEqual(transaction.req_payload, payload));
+  EXPECT_TRUE(cb);
+  transaction.response_callback = std::move(cb);
 
   // Simulate the remote's response(s)
-  async::PostTask(dispatcher_, [this, cb = std::move(cb),
-                                index = expected_transaction_index_]() {
-    size_t responses_handled = 0;
-    const Transaction& transaction = transactions_[index];
-    for (auto& response : transaction.responses) {
-      responses_handled++;
-      if (!cb(response.first, response.second)) {
-        break;
-      }
-    }
-    ASSERT_EQ(transaction.responses.size(), responses_handled);
+  async::PostTask(dispatcher_, [this, index = expected_transaction_index_]() {
+    Transaction& transaction = transactions_[index];
+    transaction.responses_handled =
+        TriggerResponses(transaction, transaction.responses);
   });
 
   expected_transaction_index_++;
   return (transaction.request_code == req_code);
 }
 
+void FakeSignalingChannel::ReceiveResponses(
+    TransactionId id,
+    const std::vector<FakeSignalingChannel::Response>& responses) {
+  if (id >= transactions_.size()) {
+    FAIL() << "Can't trigger response to unknown outbound request " << id;
+  }
+
+  const Transaction& transaction = transactions_[id];
+  {
+    ::testing::ScopedTrace trace(transaction.file, transaction.line,
+                                 "Outbound request expected here");
+    ASSERT_TRUE(transaction.response_callback)
+        << "Can't trigger responses for outbound request that hasn't been sent";
+    EXPECT_EQ(transaction.responses.size(), transaction.responses_handled)
+        << "Not all original simulated responses have been handled";
+  }
+  TriggerResponses(transaction, responses);
+}
+
 void FakeSignalingChannel::ServeRequest(CommandCode req_code,
                                         SignalingChannel::RequestDelegate cb) {
   request_handlers_[req_code] = std::move(cb);
 }
 
+FakeSignalingChannel::TransactionId FakeSignalingChannel::AddOutbound(
+    const char* file, int line, CommandCode req_code,
+    common::BufferView req_payload,
+    std::vector<FakeSignalingChannel::Response> responses) {
+  transactions_.push_back(Transaction{file, line, req_code,
+                                      std::move(req_payload),
+                                      std::move(responses), nullptr});
+  return transactions_.size() - 1;
+}
+
 void FakeSignalingChannel::ReceiveExpect(
     CommandCode req_code, const common::ByteBuffer& req_payload,
     const common::ByteBuffer& rsp_payload) {
@@ -141,6 +178,28 @@
   ReceiveExpectInternal(req_code, req_payload, &expecter);
 }
 
+size_t FakeSignalingChannel::TriggerResponses(
+    const FakeSignalingChannel::Transaction& transaction,
+    const std::vector<FakeSignalingChannel::Response>& responses) {
+  ::testing::ScopedTrace trace(transaction.file, transaction.line,
+                               "Outbound request expected here");
+  size_t responses_handled = 0;
+  for (auto& [status, payload] : responses) {
+    responses_handled++;
+    if (!transaction.response_callback(status, payload) ||
+        ::testing::Test::HasFatalFailure()) {
+      break;
+    }
+  }
+
+  EXPECT_EQ(responses.size(), responses_handled) << fxl::StringPrintf(
+      "Outbound command (code %d, at %zu) handled fewer responses than "
+      "expected",
+      transaction.request_code, transaction.responses_handled);
+
+  return responses_handled;
+}
+
 // Test evaluator for inbound requests with type-erased, bound expected requests
 void FakeSignalingChannel::ReceiveExpectInternal(
     CommandCode req_code, const common::ByteBuffer& req_payload,
diff --git a/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.h b/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.h
index 2ad952ad..4c2cdf3 100644
--- a/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.h
+++ b/garnet/drivers/bluetooth/lib/l2cap/fake_signaling_channel.h
@@ -11,6 +11,13 @@
 
 #include "garnet/drivers/bluetooth/lib/l2cap/signaling_channel.h"
 
+// Helper for FakeSignalingChannel::AddOutbound to add file and line numbers of
+// the test call site that expected the command, and to reduce one level of
+// braces in the responses. |fake_sig| should be a FakeSignalingChannel lvalue.
+#define EXPECT_OUTBOUND_REQ(fake_sig, req_code, req_payload, ...)   \
+  (fake_sig).AddOutbound(__FILE__, __LINE__, req_code, req_payload, \
+                         {__VA_ARGS__})
+
 namespace btlib {
 namespace l2cap {
 namespace internal {
@@ -21,9 +28,14 @@
 // inbound and outbound expected transactions are not synchronized.
 class FakeSignalingChannel : public SignalingChannelInterface {
  public:
+  using TransactionId = size_t;
+
+  // Simulated response's command code and payload.
+  using Response = std::pair<Status, common::BufferView>;
+
   // |dispatcher| is the test message loop's dispatcher
   explicit FakeSignalingChannel(async_dispatcher_t* dispatcher);
-  ~FakeSignalingChannel() override = default;
+  ~FakeSignalingChannel() override;
 
   // SignalingChannelInterface overrides
   bool SendRequest(CommandCode req_code, const common::ByteBuffer& payload,
@@ -31,16 +43,18 @@
   void ServeRequest(CommandCode req_code, RequestDelegate cb) override;
 
   // Add an expected outbound request, which FakeSignalingChannel will respond
-  // to with a series of responses. The request's contents will be expected to
-  // match |req_code| and |req_payload|. The corresponding request handler will
-  // be expected to handle as many responses as are provided here for testing.
-  // |responses| should be a comma-delimited list of
-  // std::pair<Status rsp_status, common::BufferView rsp_payload>.
-  template <typename... Responses>
-  void AddOutbound(CommandCode req_code, common::BufferView req_payload,
-                   Responses... responses) {
-    transactions_.push_back(Transaction{req_code, req_payload, {responses...}});
-  }
+  // to with the contents of |responses|. The request's contents will be
+  // expected to match |req_code| and |req_payload|. The request's response
+  // handler will be expected to handle all responses provided here.
+  // Returns a handle that can be used to provide additional responses with
+  // |ReceiveResponses|. |file| and |line| will be used to trace test failures.
+  TransactionId AddOutbound(const char* file, int line, CommandCode req_code,
+                            common::BufferView req_payload,
+                            std::vector<Response> responses);
+
+  // Receive additional responses to an already received request.
+  void ReceiveResponses(TransactionId id,
+                        const std::vector<Response>& responses);
 
   // Simulate reception of an inbound request with |req_code| and |req_payload|,
   // then expect a corresponding outbound response with payload |rsp_payload|.
@@ -61,13 +75,26 @@
       ChannelId local_cid, ChannelId remote_cid);
 
  private:
-  // Expected outbound request and response(s) that this fake send(s) back
+  // Expected outbound request and response(s) that this fake sends back
   struct Transaction {
-    CommandCode request_code;
-    common::BufferView req_payload;
-    std::vector<std::pair<Status, common::BufferView>> responses;
+    const char* const file;
+    const int line;
+    const CommandCode request_code;
+    const common::BufferView req_payload;
+    const std::vector<std::pair<Status, common::BufferView>> responses;
+
+    // Assigned when the request is actually sent
+    SignalingChannel::ResponseHandler response_callback = nullptr;
+
+    // Does not include responses handled in |ReceiveResponses|.
+    size_t responses_handled = 0UL;
   };
 
+  // Simulate reception of |responses|, calling |transaction.response_callback|
+  // on each response until it returns false. Returns the number of invocations.
+  size_t TriggerResponses(const Transaction& transaction,
+                          const std::vector<Response>& responses);
+
   // Test a previously-registered request handler by simulating an inbound
   // request of |req_code| and |req_payload|. The test will assert-fail if no
   // handler had been registered with |ServeRequest|. |fake_responder| will be