rib: change FIB update mocking method

Previously, FIB update mocking occurs in Rib class. Test coverage
for FibUpdater class is limited to update computation only.
Now, mocking is moved to a subclass of FibUpdater. Test coverage
for FibUpdater class extends to include FIB update queuing.

Previously, mocked FIB update succeeds synchronously.
Now, mocked FIB update completes via io.post, which better
reflects reality that FIB commands are asynchronous.

refs #4731

Change-Id: I563edda5d398eb915f22fecd4b987f32c610d323
diff --git a/daemon/rib/fib-updater.cpp b/daemon/rib/fib-updater.cpp
index 2ee14fd..6e600e4 100644
--- a/daemon/rib/fib-updater.cpp
+++ b/daemon/rib/fib-updater.cpp
@@ -35,8 +35,8 @@
 
 using ndn::nfd::ControlParameters;
 
-const unsigned int FibUpdater::MAX_NUM_TIMEOUTS = 10;
-const uint32_t FibUpdater::ERROR_FACE_NOT_FOUND = 410;
+constexpr int MAX_NUM_TIMEOUTS = 10;
+constexpr uint32_t ERROR_FACE_NOT_FOUND = 410;
 
 FibUpdater::FibUpdater(Rib& rib, ndn::nfd::Controller& controller)
   : m_rib(rib)
diff --git a/daemon/rib/fib-updater.hpp b/daemon/rib/fib-updater.hpp
index a404853..518b9c7 100644
--- a/daemon/rib/fib-updater.hpp
+++ b/daemon/rib/fib-updater.hpp
@@ -48,13 +48,15 @@
   };
 
 public:
-  typedef std::list<FibUpdate> FibUpdateList;
-
-  typedef std::function<void(RibUpdateList inheritedRoutes)> FibUpdateSuccessCallback;
-  typedef std::function<void(uint32_t code, const std::string& error)> FibUpdateFailureCallback;
+  using FibUpdateList = std::list<FibUpdate>;
+  using FibUpdateSuccessCallback = std::function<void(RibUpdateList inheritedRoutes)>;
+  using FibUpdateFailureCallback = std::function<void(uint32_t code, const std::string& error)>;
 
   FibUpdater(Rib& rib, ndn::nfd::Controller& controller);
 
+  VIRTUAL_WITH_TESTS
+  ~FibUpdater() = default;
+
   /** \brief computes FibUpdates using the provided RibUpdateBatch and then sends the
    *         updates to NFD's FIB
    *
@@ -66,7 +68,7 @@
                            const FibUpdateSuccessCallback& onSuccess,
                            const FibUpdateFailureCallback& onFailure);
 
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+private:
   /** \brief determines the type of action that will be performed on the RIB and calls the
   *          corresponding computation method
   */
@@ -100,12 +102,13 @@
   sendUpdatesForNonBatchFaceId(const FibUpdateSuccessCallback& onSuccess,
                                const FibUpdateFailureCallback& onFailure);
 
+PROTECTED_WITH_TESTS_ELSE_PRIVATE:
   /** \brief sends a FibAddNextHopCommand to NFD using the parameters supplied by
   *          the passed update
   *
   *   \param nTimeouts the number of times this FibUpdate has failed due to timeout
   */
-  void
+  VIRTUAL_WITH_TESTS void
   sendAddNextHopUpdate(const FibUpdate& update,
                        const FibUpdateSuccessCallback& onSuccess,
                        const FibUpdateFailureCallback& onFailure,
@@ -116,7 +119,7 @@
   *
   *   \param nTimeouts the number of times this FibUpdate has failed due to timeout
   */
-  void
+  VIRTUAL_WITH_TESTS void
   sendRemoveNextHopUpdate(const FibUpdate& update,
                           const FibUpdateSuccessCallback& onSuccess,
                           const FibUpdateFailureCallback& onFailure,
@@ -133,7 +136,7 @@
   void
   computeUpdatesForUnregistration(const RibUpdate& update);
 
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+PROTECTED_WITH_TESTS_ELSE_PRIVATE:
   /** \brief callback used by NfdController when a FibAddNextHopCommand or FibRemoveNextHopCommand
   *          is successful.
   *
@@ -239,7 +242,6 @@
   void
   traverseSubTree(const RibEntry& entry, Rib::RouteSet routesToAdd, Rib::RouteSet routesToRemove);
 
-private:
   /** \brief creates a record of a calculated inherited route that should be added to the entry
   */
   void
@@ -263,10 +265,6 @@
    *         passed to the RIB when updates are completed successfully
    */
   RibUpdateList m_inheritedRoutes;
-
-private:
-  static const unsigned int MAX_NUM_TIMEOUTS;
-  static const uint32_t ERROR_FACE_NOT_FOUND;
 };
 
 } // namespace rib
diff --git a/daemon/rib/rib.cpp b/daemon/rib/rib.cpp
index 0f099d6..2d1db7f 100644
--- a/daemon/rib/rib.cpp
+++ b/daemon/rib/rib.cpp
@@ -45,12 +45,6 @@
   return lhs.faceId < rhs.faceId;
 }
 
-Rib::Rib()
-  : m_nItems(0)
-  , m_isUpdateInProgress(false)
-{
-}
-
 void
 Rib::setFibUpdater(FibUpdater* updater)
 {
@@ -435,23 +429,6 @@
   auto fibSuccessCb = bind(&Rib::onFibUpdateSuccess, this, batch, _1, item.managerSuccessCallback);
   auto fibFailureCb = bind(&Rib::onFibUpdateFailure, this, item.managerFailureCallback, _1, _2);
 
-#ifdef WITH_TESTS
-  if (mockFibResponse != nullptr) {
-    m_fibUpdater->computeAndSendFibUpdates(batch, bind([]{}), bind([]{}));
-    bool shouldFibSucceed = mockFibResponse(batch);
-    if (wantMockFibResponseOnce) {
-      mockFibResponse = nullptr;
-    }
-    if (shouldFibSucceed) {
-      fibSuccessCb(m_fibUpdater->m_inheritedRoutes);
-    }
-    else {
-      fibFailureCb(504, "mocked failure");
-    }
-    return;
-  }
-#endif
-
   m_fibUpdater->computeAndSendFibUpdates(batch, fibSuccessCb, fibFailureCb);
 }
 
diff --git a/daemon/rib/rib.hpp b/daemon/rib/rib.hpp
index 529350c..010ea89 100644
--- a/daemon/rib/rib.hpp
+++ b/daemon/rib/rib.hpp
@@ -59,13 +59,9 @@
 class Rib : noncopyable
 {
 public:
-  typedef std::list<shared_ptr<RibEntry>> RibEntryList;
-  typedef std::map<Name, shared_ptr<RibEntry>> RibTable;
-  typedef RibTable::const_iterator const_iterator;
-  typedef bool (*RouteComparePredicate)(const Route&, const Route&);
-  typedef std::set<Route, RouteComparePredicate> RouteSet;
-
-  Rib();
+  using RibEntryList = std::list<shared_ptr<RibEntry>>;
+  using RibTable = std::map<Name, shared_ptr<RibEntry>>;
+  using const_iterator = RibTable::const_iterator;
 
   void
   setFibUpdater(FibUpdater* updater);
@@ -80,16 +76,28 @@
   findLongestPrefix(const Name& prefix, const Route& route) const;
 
   const_iterator
-  begin() const;
+  begin() const
+  {
+    return m_rib.begin();
+  }
 
   const_iterator
-  end() const;
+  end() const
+  {
+    return m_rib.end();
+  }
 
   size_t
-  size() const;
+  size() const
+  {
+    return m_nItems;
+  }
 
   bool
-  empty() const;
+  empty() const
+  {
+    return m_rib.empty();
+  }
 
   shared_ptr<RibEntry>
   findParent(const Name& prefix) const;
@@ -153,21 +161,13 @@
                      uint32_t code, const std::string& error);
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-#ifdef WITH_TESTS
-  /** \brief In unit tests, mock FIB update result.
-   *
-   *  If the callback is not nullptr, sendBatchFromQueue() immediately succeeds or fails according
-   *  to the return value of callback function.
-   */
-  std::function<bool(const RibUpdateBatch&)> mockFibResponse;
-
-  bool wantMockFibResponseOnce = false; ///< if true, mockFibResponse is cleared after every use.
-#endif
-
   void
   erase(const Name& prefix, const Route& route);
 
 private:
+  using RouteComparePredicate = bool (*)(const Route&, const Route&);
+  using RouteSet = std::set<Route, RouteComparePredicate>;
+
   /** \brief find entries under \p prefix
    *  \pre a RIB entry exists at \p prefix
    */
@@ -214,34 +214,29 @@
    *  A RIB entry is inserted when the first route associated with a
    *  certain namespace is added.
    */
-  ndn::util::signal::Signal<Rib, Name> afterInsertEntry;
+  signal::Signal<Rib, Name> afterInsertEntry;
 
   /** \brief signals after a RIB entry is erased
    *
    *  A RIB entry is erased when the last route associated with a
    *  certain namespace is removed.
    */
-
-  ndn::util::signal::Signal<Rib, Name> afterEraseEntry;
+  signal::Signal<Rib, Name> afterEraseEntry;
 
   /** \brief signals after a Route is added
    */
-  ndn::util::signal::Signal<Rib, RibRouteRef> afterAddRoute;
+  signal::Signal<Rib, RibRouteRef> afterAddRoute;
 
   /** \brief signals before a route is removed
    */
-  ndn::util::signal::Signal<Rib, RibRouteRef> beforeRemoveRoute;
+  signal::Signal<Rib, RibRouteRef> beforeRemoveRoute;
 
 private:
   RibTable m_rib;
   std::multimap<uint64_t, shared_ptr<RibEntry>> m_faceEntries; ///< FaceId => Entry with Route on this face
-  FibUpdater* m_fibUpdater;
+  size_t m_nItems = 0;
+  FibUpdater* m_fibUpdater = nullptr;
 
-  size_t m_nItems;
-
-  friend class FibUpdater;
-
-private:
   struct UpdateQueueItem
   {
     RibUpdateBatch batch;
@@ -249,38 +244,13 @@
     const Rib::UpdateFailureCallback managerFailureCallback;
   };
 
-PUBLIC_WITH_TESTS_ELSE_PRIVATE:
-  typedef std::list<UpdateQueueItem> UpdateQueue;
+  using UpdateQueue = std::list<UpdateQueueItem>;
   UpdateQueue m_updateBatches;
+  bool m_isUpdateInProgress = false;
 
-private:
-  bool m_isUpdateInProgress;
+  friend class FibUpdater;
 };
 
-inline Rib::const_iterator
-Rib::begin() const
-{
-  return m_rib.begin();
-}
-
-inline Rib::const_iterator
-Rib::end() const
-{
-  return m_rib.end();
-}
-
-inline size_t
-Rib::size() const
-{
-  return m_nItems;
-}
-
-inline bool
-Rib::empty() const
-{
-  return m_rib.empty();
-}
-
 std::ostream&
 operator<<(std::ostream& os, const Rib& rib);
 
diff --git a/tests/daemon/global-io-fixture.cpp b/tests/daemon/global-io-fixture.cpp
index 9d9b184..e4f40fe 100644
--- a/tests/daemon/global-io-fixture.cpp
+++ b/tests/daemon/global-io-fixture.cpp
@@ -39,6 +39,19 @@
   resetGlobalIoService();
 }
 
+size_t
+GlobalIoFixture::pollIo()
+{
+  if (g_io.stopped()) {
+#if BOOST_VERSION >= 106600
+    g_io.restart();
+#else
+    g_io.reset();
+#endif
+  }
+  return g_io.poll();
+}
+
 GlobalIoTimeFixture::GlobalIoTimeFixture()
   : ClockFixture(g_io)
 {
diff --git a/tests/daemon/global-io-fixture.hpp b/tests/daemon/global-io-fixture.hpp
index 6dbc891..544c466 100644
--- a/tests/daemon/global-io-fixture.hpp
+++ b/tests/daemon/global-io-fixture.hpp
@@ -43,6 +43,11 @@
 
   ~GlobalIoFixture();
 
+  /** \brief Poll the global io_service.
+   */
+  size_t
+  pollIo();
+
 protected:
   /** \brief Reference to the global io_service instance.
    */
diff --git a/tests/daemon/mgmt/rib-manager-sl-announce.t.cpp b/tests/daemon/mgmt/rib-manager-sl-announce.t.cpp
index 2afa8fd..83d7a64 100644
--- a/tests/daemon/mgmt/rib-manager-sl-announce.t.cpp
+++ b/tests/daemon/mgmt/rib-manager-sl-announce.t.cpp
@@ -24,11 +24,11 @@
  */
 
 #include "mgmt/rib-manager.hpp"
-#include "rib/fib-updater.hpp"
 
 #include "tests/test-common.hpp"
 #include "tests/key-chain-fixture.hpp"
 #include "tests/daemon/global-io-fixture.hpp"
+#include "tests/daemon/rib/fib-updates-common.hpp"
 
 #include <ndn-cxx/util/dummy-client-face.hpp>
 
@@ -50,9 +50,6 @@
     , m_trustedSigner(m_keyChain.createIdentity("/trusted", ndn::RsaKeyParams()))
     , m_untrustedSigner(m_keyChain.createIdentity("/untrusted", ndn::RsaKeyParams()))
   {
-    rib.mockFibResponse = [] (const auto&) { return true; };
-    rib.wantMockFibResponseOnce = false;
-
     // Face, Controller, Dispatcher are irrelevant to SlAnnounce functions but required by
     // RibManager construction, so they are private. RibManager is a pointer to avoid code style
     // rule 1.4 violation.
@@ -166,7 +163,7 @@
   ndn::util::DummyClientFace m_face;
   ndn::nfd::Controller m_nfdController;
   Dispatcher m_dispatcher;
-  rib::FibUpdater m_fibUpdater;
+  rib::tests::MockFibUpdater m_fibUpdater;
 
   ndn::security::SigningInfo m_trustedSigner;
   ndn::security::SigningInfo m_untrustedSigner;
diff --git a/tests/daemon/mgmt/rib-manager.t.cpp b/tests/daemon/mgmt/rib-manager.t.cpp
index 70369a7..4bf919e 100644
--- a/tests/daemon/mgmt/rib-manager.t.cpp
+++ b/tests/daemon/mgmt/rib-manager.t.cpp
@@ -24,9 +24,9 @@
  */
 
 #include "mgmt/rib-manager.hpp"
-#include "rib/fib-updater.hpp"
 
 #include "manager-common-fixture.hpp"
+#include "tests/daemon/rib/fib-updates-common.hpp"
 
 #include <ndn-cxx/lp/tags.hpp>
 #include <ndn-cxx/mgmt/nfd/face-status.hpp>
@@ -53,18 +53,11 @@
 {
 public:
   RibManagerFixture(const ConfigurationStatus& status, bool shouldClearRib)
-    : m_commands(m_face.sentInterests)
-    , m_status(status)
+    : m_status(status)
     , m_nfdController(m_face, m_keyChain)
     , m_fibUpdater(m_rib, m_nfdController)
     , m_manager(m_rib, m_face, m_keyChain, m_nfdController, m_dispatcher)
   {
-    m_rib.mockFibResponse = [] (const rib::RibUpdateBatch& batch) {
-      BOOST_CHECK(batch.begin() != batch.end());
-      return true;
-    };
-    m_rib.wantMockFibResponseOnce = false;
-
     if (m_status.isLocalhostConfigured) {
       m_manager.applyLocalhostConfig(getValidatorConfigSection(), "test");
     }
@@ -105,7 +98,7 @@
     };
 
     Name commandPrefix("/localhost/nfd/fib/add-nexthop");
-    for (const auto& command : m_commands) {
+    for (const auto& command : m_face.sentInterests) {
       if (commandPrefix.isPrefixOf(command.getName())) {
         replyFibAddCommand(command);
         advanceClocks(1_ms);
@@ -114,7 +107,7 @@
 
     // clear commands and responses
     m_responses.clear();
-    m_commands.clear();
+    m_face.sentInterests.clear();
   }
 
   void
@@ -148,91 +141,15 @@
       .setOrigin(ndn::nfd::ROUTE_ORIGIN_NLSR);
   }
 
-public:
-  enum class CheckCommandResult {
-    OK,
-    OUT_OF_BOUNDARY,
-    WRONG_FORMAT,
-    WRONG_VERB,
-    WRONG_PARAMS_FORMAT,
-    WRONG_PARAMS_NAME,
-    WRONG_PARAMS_FACE
-  };
-
-  CheckCommandResult
-  checkCommand(size_t idx, const char* verbStr, const ControlParameters& expectedParams) const
-  {
-    if (idx > m_commands.size()) {
-      return CheckCommandResult::OUT_OF_BOUNDARY;
-    }
-    const auto& name = m_commands[idx].getName();
-
-    if (!FIB_COMMAND_PREFIX.isPrefixOf(name) || FIB_COMMAND_PREFIX.size() >= name.size()) {
-      return CheckCommandResult::WRONG_FORMAT;
-    }
-    const auto& verb = name[FIB_COMMAND_PREFIX.size()];
-
-    Name::Component expectedVerb(verbStr);
-    if (verb != expectedVerb) {
-      return CheckCommandResult::WRONG_VERB;
-    }
-
-    ControlParameters parameters;
-    try {
-      Block rawParameters = name[FIB_COMMAND_PREFIX.size() + 1].blockFromValue();
-      parameters.wireDecode(rawParameters);
-    }
-    catch (...) {
-      return CheckCommandResult::WRONG_PARAMS_FORMAT;
-    }
-
-    if (parameters.getName() != expectedParams.getName()) {
-      return CheckCommandResult::WRONG_PARAMS_NAME;
-    }
-
-    if (parameters.getFaceId() != expectedParams.getFaceId()) {
-      return CheckCommandResult::WRONG_PARAMS_FACE;
-    }
-
-    return CheckCommandResult::OK;
-  }
-
 protected:
-  static const Name FIB_COMMAND_PREFIX;
-  std::vector<Interest>& m_commands;
   ConfigurationStatus m_status;
 
   ndn::nfd::Controller m_nfdController;
   rib::Rib m_rib;
-  rib::FibUpdater m_fibUpdater;
+  rib::tests::MockFibUpdater m_fibUpdater;
   RibManager m_manager;
 };
 
-const Name RibManagerFixture::FIB_COMMAND_PREFIX("/localhost/nfd/fib");
-
-std::ostream&
-operator<<(std::ostream& os, RibManagerFixture::CheckCommandResult result)
-{
-  switch (result) {
-  case RibManagerFixture::CheckCommandResult::OK:
-    return os << "OK";
-  case RibManagerFixture::CheckCommandResult::OUT_OF_BOUNDARY:
-    return os << "OUT_OF_BOUNDARY";
-  case RibManagerFixture::CheckCommandResult::WRONG_FORMAT:
-    return os << "WRONG_FORMAT";
-  case RibManagerFixture::CheckCommandResult::WRONG_VERB:
-    return os << "WRONG_VERB";
-  case RibManagerFixture::CheckCommandResult::WRONG_PARAMS_FORMAT:
-    return os << "WRONG_PARAMS_FORMAT";
-  case RibManagerFixture::CheckCommandResult::WRONG_PARAMS_NAME:
-    return os << "WRONG_PARAMS_NAME";
-  case RibManagerFixture::CheckCommandResult::WRONG_PARAMS_FACE:
-    return os << "WRONG_PARAMS_FACE";
-  };
-
-  return os << static_cast<int>(result);
-}
-
 BOOST_AUTO_TEST_SUITE(Mgmt)
 BOOST_AUTO_TEST_SUITE(TestRibManager)
 
@@ -346,9 +263,11 @@
   BOOST_CHECK_EQUAL(checkResponse(1, commandUnregister.getName(), makeResponse(200, "Success", paramsUnregister)),
                     CheckResponseResult::OK);
 
-  BOOST_REQUIRE_EQUAL(m_commands.size(), 2);
-  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
-  BOOST_CHECK_EQUAL(checkCommand(1, "remove-nexthop", paramsUnregister), CheckCommandResult::OK);
+  BOOST_REQUIRE_EQUAL(m_fibUpdater.updates.size(), 2);
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.front(),
+                    rib::FibUpdate::createAddUpdate("/test-register-unregister", 9527, 10));
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.back(),
+                    rib::FibUpdate::createRemoveUpdate("/test-register-unregister", 9527));
 }
 
 BOOST_AUTO_TEST_CASE(SelfOperation)
@@ -376,9 +295,11 @@
   BOOST_CHECK_EQUAL(checkResponse(1, commandUnregister.getName(), makeResponse(200, "Success", paramsUnregister)),
                     CheckResponseResult::OK);
 
-  BOOST_REQUIRE_EQUAL(m_commands.size(), 2);
-  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
-  BOOST_CHECK_EQUAL(checkCommand(1, "remove-nexthop", paramsUnregister), CheckCommandResult::OK);
+  BOOST_REQUIRE_EQUAL(m_fibUpdater.updates.size(), 2);
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.front(),
+                    rib::FibUpdate::createAddUpdate("/test-self-register-unregister", 9527, 10));
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.back(),
+                    rib::FibUpdate::createRemoveUpdate("/test-self-register-unregister", 9527));
 }
 
 BOOST_AUTO_TEST_CASE(Expiration)
@@ -388,17 +309,20 @@
   receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", paramsRegister));
 
   advanceClocks(55_ms);
-  BOOST_REQUIRE_EQUAL(m_commands.size(), 2); // the registered route has expired
-  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
-  BOOST_CHECK_EQUAL(checkCommand(1, "remove-nexthop", paramsUnregister), CheckCommandResult::OK);
+  BOOST_REQUIRE_EQUAL(m_fibUpdater.updates.size(), 2); // the registered route has expired
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.front(),
+                    rib::FibUpdate::createAddUpdate("/test-expiry", 9527, 10));
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.back(),
+                    rib::FibUpdate::createRemoveUpdate("/test-expiry", 9527));
 
-  m_commands.clear();
+  m_fibUpdater.updates.clear();
   paramsRegister.setExpirationPeriod(100_ms);
   receiveInterest(makeControlCommandRequest("/localhost/nfd/rib/register", paramsRegister));
 
   advanceClocks(55_ms);
-  BOOST_REQUIRE_EQUAL(m_commands.size(), 1); // the registered route is still active
-  BOOST_CHECK_EQUAL(checkCommand(0, "add-nexthop", paramsRegister), CheckCommandResult::OK);
+  BOOST_REQUIRE_EQUAL(m_fibUpdater.updates.size(), 1); // the registered route is still active
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.front(),
+                    rib::FibUpdate::createAddUpdate("/test-expiry", 9527, 10));
 }
 
 BOOST_AUTO_TEST_CASE(NameTooLong)
@@ -417,7 +341,7 @@
                                                   to_string(Fib::getMaxDepth()) + " components")),
                     CheckResponseResult::OK);
 
-  BOOST_CHECK_EQUAL(m_commands.size(), 0);
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.size(), 0);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // RegisterUnregister
@@ -483,12 +407,12 @@
 
 BOOST_AUTO_TEST_CASE(FetchActiveFacesEvent)
 {
-  BOOST_CHECK_EQUAL(m_commands.size(), 0);
+  BOOST_CHECK_EQUAL(m_fibUpdater.updates.size(), 0);
 
   advanceClocks(301_s); // RibManager::ACTIVE_FACE_FETCH_INTERVAL = 300s
-  BOOST_REQUIRE_EQUAL(m_commands.size(), 2);
-  BOOST_CHECK_EQUAL(m_commands[0].getName(), "/localhost/nfd/faces/events");
-  BOOST_CHECK_EQUAL(m_commands[1].getName(), "/localhost/nfd/faces/list");
+  BOOST_REQUIRE_EQUAL(m_face.sentInterests.size(), 2);
+  BOOST_CHECK_EQUAL(m_face.sentInterests[0].getName(), "/localhost/nfd/faces/events");
+  BOOST_CHECK_EQUAL(m_face.sentInterests[1].getName(), "/localhost/nfd/faces/list");
 }
 
 BOOST_AUTO_TEST_CASE(RemoveInvalidFaces)
diff --git a/tests/daemon/rib/fib-updates-common.hpp b/tests/daemon/rib/fib-updates-common.hpp
index 9afd4a5..1930d14 100644
--- a/tests/daemon/rib/fib-updates-common.hpp
+++ b/tests/daemon/rib/fib-updates-common.hpp
@@ -27,6 +27,7 @@
 #define NFD_TESTS_DAEMON_RIB_FIB_UPDATES_COMMON_HPP
 
 #include "rib/fib-updater.hpp"
+#include "common/global.hpp"
 
 #include "tests/key-chain-fixture.hpp"
 #include "tests/daemon/global-io-fixture.hpp"
@@ -40,6 +41,62 @@
 
 using namespace nfd::tests;
 
+class MockFibUpdater : public FibUpdater
+{
+public:
+  using FibUpdater::FibUpdater;
+
+  void
+  sortUpdates()
+  {
+    updates.sort([] (const auto& lhs, const auto& rhs) {
+      return std::tie(lhs.name, lhs.faceId, lhs.cost, lhs.action) <
+             std::tie(rhs.name, rhs.faceId, rhs.cost, rhs.action);
+    });
+  }
+
+private:
+  void
+  sendAddNextHopUpdate(const FibUpdate& update,
+                       const FibUpdateSuccessCallback& onSuccess,
+                       const FibUpdateFailureCallback& onFailure,
+                       uint32_t nTimeouts) override
+  {
+    mockUpdate(update, onSuccess, onFailure, nTimeouts);
+  }
+
+  void
+  sendRemoveNextHopUpdate(const FibUpdate& update,
+                          const FibUpdateSuccessCallback& onSuccess,
+                          const FibUpdateFailureCallback& onFailure,
+                          uint32_t nTimeouts) override
+  {
+    mockUpdate(update, onSuccess, onFailure, nTimeouts);
+  }
+
+  void
+  mockUpdate(const FibUpdate& update,
+             const FibUpdateSuccessCallback& onSuccess,
+             const FibUpdateFailureCallback& onFailure,
+             uint32_t nTimeouts)
+  {
+    updates.push_back(update);
+    getGlobalIoService().post([=] {
+      if (mockSuccess) {
+        onUpdateSuccess(update, onSuccess, onFailure);
+      }
+      else {
+        ndn::mgmt::ControlResponse resp(504, "mocked failure");
+        onUpdateError(update, onSuccess, onFailure, resp, nTimeouts);
+      }
+    });
+  }
+
+public:
+  FibUpdateList updates;
+  bool mockSuccess = true;
+};
+
 class FibUpdatesFixture : public GlobalIoFixture, public KeyChainFixture
 {
 public:
@@ -63,8 +120,8 @@
           .setName(name)
           .setRoute(route);
 
-    simulateSuccessfulResponse();
     rib.beginApplyUpdate(update, nullptr, nullptr);
+    pollIo();
   }
 
   void
@@ -78,52 +135,34 @@
           .setName(name)
           .setRoute(route);
 
-    simulateSuccessfulResponse();
     rib.beginApplyUpdate(update, nullptr, nullptr);
+    pollIo();
   }
 
   void
   destroyFace(uint64_t faceId)
   {
-    simulateSuccessfulResponse();
     rib.beginRemoveFace(faceId);
+    pollIo();
   }
 
   const FibUpdater::FibUpdateList&
-  getFibUpdates()
+  getFibUpdates() const
   {
-    fibUpdates.clear();
-    fibUpdates = fibUpdater.m_updatesForBatchFaceId;
-    fibUpdates.insert(fibUpdates.end(), fibUpdater.m_updatesForNonBatchFaceId.begin(),
-                                        fibUpdater.m_updatesForNonBatchFaceId.end());
-
-    return fibUpdates;
+    return fibUpdater.updates;
   }
 
-  FibUpdater::FibUpdateList
+  const FibUpdater::FibUpdateList&
   getSortedFibUpdates()
   {
-    FibUpdater::FibUpdateList updates = getFibUpdates();
-    updates.sort([] (const auto& lhs, const auto& rhs) {
-      return std::tie(lhs.name, lhs.faceId, lhs.cost, lhs.action) <
-             std::tie(rhs.name, rhs.faceId, rhs.cost, rhs.action);
-    });
-    return updates;
+    fibUpdater.sortUpdates();
+    return fibUpdater.updates;
   }
 
   void
   clearFibUpdates()
   {
-    fibUpdater.m_updatesForBatchFaceId.clear();
-    fibUpdater.m_updatesForNonBatchFaceId.clear();
-  }
-
-private:
-  void
-  simulateSuccessfulResponse()
-  {
-    rib.mockFibResponse = [] (const RibUpdateBatch&) { return true; };
-    rib.wantMockFibResponseOnce = true;
+    fibUpdater.updates.clear();
   }
 
 public:
@@ -131,8 +170,7 @@
   ndn::nfd::Controller controller;
 
   Rib rib;
-  FibUpdater fibUpdater;
-  FibUpdater::FibUpdateList fibUpdates;
+  MockFibUpdater fibUpdater;
 };
 
 } // namespace tests