tests: sync common testing infrastructure with ndn-cxx

Change-Id: I1bd56d472f5627207f17c950eb4c5550d1b3724c
diff --git a/tests/clock-fixture.cpp b/tests/clock-fixture.cpp
new file mode 100644
index 0000000..2902118
--- /dev/null
+++ b/tests/clock-fixture.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2022 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "tests/clock-fixture.hpp"
+
+namespace ndncert::tests {
+
+ClockFixture::ClockFixture()
+  : m_steadyClock(std::make_shared<time::UnitTestSteadyClock>())
+  , m_systemClock(std::make_shared<time::UnitTestSystemClock>())
+{
+  time::setCustomClocks(m_steadyClock, m_systemClock);
+}
+
+ClockFixture::~ClockFixture()
+{
+  time::setCustomClocks(nullptr, nullptr);
+}
+
+void
+ClockFixture::advanceClocks(time::nanoseconds tick, time::nanoseconds total)
+{
+  BOOST_ASSERT(tick > time::nanoseconds::zero());
+  BOOST_ASSERT(total >= time::nanoseconds::zero());
+
+  while (total > time::nanoseconds::zero()) {
+    auto t = std::min(tick, total);
+    m_steadyClock->advance(t);
+    m_systemClock->advance(t);
+    total -= t;
+
+    afterTick();
+  }
+}
+
+} // namespace ndncert::tests
diff --git a/tests/clock-fixture.hpp b/tests/clock-fixture.hpp
new file mode 100644
index 0000000..fbf76c6
--- /dev/null
+++ b/tests/clock-fixture.hpp
@@ -0,0 +1,85 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2022 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDNCERT_TESTS_CLOCK_FIXTURE_HPP
+#define NDNCERT_TESTS_CLOCK_FIXTURE_HPP
+
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+
+namespace ndncert::tests {
+
+namespace time = ndn::time;
+
+/** \brief A test fixture that overrides steady clock and system clock.
+ */
+class ClockFixture
+{
+public:
+  virtual
+  ~ClockFixture();
+
+  /** \brief Advance steady and system clocks.
+   *
+   *  Clocks are advanced in increments of \p tick for \p nTicks ticks.
+   *  afterTick() is called after each tick.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancement will stop in the event of an exception.
+   */
+  void
+  advanceClocks(time::nanoseconds tick, size_t nTicks = 1)
+  {
+    advanceClocks(tick, tick * nTicks);
+  }
+
+  /** \brief Advance steady and system clocks.
+   *
+   *  Clocks are advanced in increments of \p tick for \p total time.
+   *  The last increment might be shorter than \p tick.
+   *  afterTick() is called after each tick.
+   *
+   *  Exceptions thrown during I/O events are propagated to the caller.
+   *  Clock advancement will stop in the event of an exception.
+   */
+  void
+  advanceClocks(time::nanoseconds tick, time::nanoseconds total);
+
+protected:
+  ClockFixture();
+
+private:
+  /** \brief Called by advanceClocks() after each clock advancement (tick).
+   *
+   *  The base class implementation is a no-op.
+   */
+  virtual void
+  afterTick()
+  {
+  }
+
+protected:
+  std::shared_ptr<time::UnitTestSteadyClock> m_steadyClock;
+  std::shared_ptr<time::UnitTestSystemClock> m_systemClock;
+};
+
+} // namespace ndncert::tests
+
+#endif // NDNCERT_TESTS_CLOCK_FIXTURE_HPP
diff --git a/tests/database-fixture.hpp b/tests/database-fixture.hpp
deleted file mode 100644
index 283ae2b..0000000
--- a/tests/database-fixture.hpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Copyright (c) 2013-2022 Regents of the University of California.
- *
- * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
- *
- * ndn-cxx library is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Lesser General Public License as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
- *
- * You should have received copies of the GNU General Public License and GNU Lesser
- * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- */
-
-#ifndef NDNCERT_TESTS_DATABASE_FIXTURE_HPP
-#define NDNCERT_TESTS_DATABASE_FIXTURE_HPP
-
-#include "identity-management-fixture.hpp"
-#include "unit-test-time-fixture.hpp"
-
-#include <boost/filesystem.hpp>
-
-namespace ndncert::tests {
-
-class IdentityManagementTimeFixture : public UnitTestTimeFixture
-                                    , public IdentityManagementFixture
-{
-};
-
-class DatabaseFixture : public IdentityManagementTimeFixture
-{
-public:
-  DatabaseFixture()
-  {
-    boost::filesystem::path parentDir{TMP_TESTS_PATH};
-    dbDir = parentDir / "test-home" / ".ndncert";
-    if (!boost::filesystem::exists(dbDir)) {
-      boost::filesystem::create_directory(dbDir);
-    }
-  }
-
-  ~DatabaseFixture()
-  {
-    boost::filesystem::remove_all(dbDir);
-  }
-
-protected:
-  boost::filesystem::path dbDir;
-};
-
-} // namespace ndncert::tests
-
-#endif // NDNCERT_TESTS_DATABASE_FIXTURE_HPP
diff --git a/tests/global-configuration.cpp b/tests/global-configuration.cpp
index bad7caf..4dad5c0 100644
--- a/tests/global-configuration.cpp
+++ b/tests/global-configuration.cpp
@@ -20,7 +20,7 @@
 
 #include "detail/ndncert-common.hpp"
 
-#include "boost-test.hpp"
+#include "tests/boost-test.hpp"
 
 #include <boost/filesystem.hpp>
 #include <fstream>
@@ -37,7 +37,7 @@
     if (envHome)
       m_home = envHome;
 
-    auto testHome = boost::filesystem::path(TMP_TESTS_PATH) / "test-home";
+    auto testHome = boost::filesystem::path(UNIT_TESTS_TMPDIR) / "test-home";
     if (::setenv("HOME", testHome.c_str(), 1) != 0)
       NDN_THROW(std::runtime_error("setenv() failed"));
 
diff --git a/tests/identity-management-fixture.cpp b/tests/identity-management-fixture.cpp
deleted file mode 100644
index 86655c4..0000000
--- a/tests/identity-management-fixture.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Copyright (c) 2013-2022 Regents of the University of California.
- *
- * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
- *
- * ndn-cxx library is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Lesser General Public License as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
- *
- * You should have received copies of the GNU General Public License and GNU Lesser
- * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- */
-
-#include "identity-management-fixture.hpp"
-
-#include <ndn-cxx/security/additional-description.hpp>
-#include <ndn-cxx/util/io.hpp>
-
-#include <boost/filesystem.hpp>
-
-namespace ndncert::tests {
-
-using namespace ndn::security;
-
-IdentityManagementBaseFixture::~IdentityManagementBaseFixture()
-{
-  boost::system::error_code ec;
-  for (const auto& certFile : m_certFiles) {
-    boost::filesystem::remove(certFile, ec); // ignore error
-  }
-}
-
-bool
-IdentityManagementBaseFixture::saveCertToFile(const Data& obj, const std::string& filename)
-{
-  m_certFiles.insert(filename);
-  try {
-    ndn::io::save(obj, filename);
-    return true;
-  }
-  catch (const ndn::io::Error&) {
-    return false;
-  }
-}
-
-IdentityManagementFixture::IdentityManagementFixture()
-  : m_keyChain("pib-memory:", "tpm-memory:")
-{
-}
-
-Identity
-IdentityManagementFixture::addIdentity(const Name& identityName, const ndn::KeyParams& params)
-{
-  auto identity = m_keyChain.createIdentity(identityName, params);
-  m_identities.insert(identityName);
-  return identity;
-}
-
-bool
-IdentityManagementFixture::saveCertificate(const Identity& identity, const std::string& filename)
-{
-  try {
-    auto cert = identity.getDefaultKey().getDefaultCertificate();
-    return saveCertToFile(cert, filename);
-  }
-  catch (const Pib::Error&) {
-    return false;
-  }
-}
-
-Identity
-IdentityManagementFixture::addSubCertificate(const Name& subIdentityName,
-                                             const Identity& issuer, const ndn::KeyParams& params)
-{
-  auto subIdentity = addIdentity(subIdentityName, params);
-
-  Certificate request = subIdentity.getDefaultKey().getDefaultCertificate();
-  request.setName(request.getKeyName().append("parent").appendVersion());
-
-  SignatureInfo info;
-  auto now = time::system_clock::now();
-  info.setValidityPeriod(ValidityPeriod(now, now + 7300_days));
-
-  AdditionalDescription description;
-  description.set("type", "sub-certificate");
-  info.addCustomTlv(description.wireEncode());
-
-  m_keyChain.sign(request, signingByIdentity(issuer).setSignatureInfo(info));
-  m_keyChain.setDefaultCertificate(subIdentity.getDefaultKey(), request);
-
-  return subIdentity;
-}
-
-Certificate
-IdentityManagementFixture::addCertificate(const Key& key, const std::string& issuer)
-{
-  Name certificateName = key.getName();
-  certificateName
-    .append(issuer)
-    .appendVersion();
-  Certificate certificate;
-  certificate.setName(certificateName);
-
-  // set metainfo
-  certificate.setContentType(ndn::tlv::ContentType_Key);
-  certificate.setFreshnessPeriod(1_h);
-
-  // set content
-  certificate.setContent(key.getPublicKey());
-
-  // set signature-info
-  SignatureInfo info;
-  auto now = time::system_clock::now();
-  info.setValidityPeriod(ValidityPeriod(now, now + 10_days));
-
-  m_keyChain.sign(certificate, signingByKey(key).setSignatureInfo(info));
-  return certificate;
-}
-
-} // namespace ndncert::tests
diff --git a/tests/identity-management-fixture.hpp b/tests/identity-management-fixture.hpp
deleted file mode 100644
index 8c435a4..0000000
--- a/tests/identity-management-fixture.hpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Copyright (c) 2013-2022 Regents of the University of California.
- *
- * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
- *
- * ndn-cxx library is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Lesser General Public License as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
- *
- * You should have received copies of the GNU General Public License and GNU Lesser
- * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- */
-
-#ifndef NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
-#define NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
-
-#include "detail/ndncert-common.hpp"
-
-#include <ndn-cxx/security/key-chain.hpp>
-#include <ndn-cxx/security/signing-helpers.hpp>
-
-namespace ndncert::tests {
-
-class IdentityManagementBaseFixture
-{
-public:
-  ~IdentityManagementBaseFixture();
-
-  bool
-  saveCertToFile(const Data& obj, const std::string& filename);
-
-protected:
-  std::set<Name> m_identities;
-  std::set<std::string> m_certFiles;
-};
-
-/**
- * @brief A test suite level fixture to help with identity management
- *
- * Test cases in the suite can use this fixture to create identities.  Identities,
- * certificates, and saved certificates are automatically removed during test teardown.
- */
-class IdentityManagementFixture : public IdentityManagementBaseFixture
-{
-protected:
-  using Identity = ndn::security::Identity;
-  using Key      = ndn::security::Key;
-
-public:
-  IdentityManagementFixture();
-
-  /**
-   * @brief Add identity @p identityName
-   * @return name of the created self-signed certificate
-   */
-  Identity
-  addIdentity(const Name& identityName,
-              const ndn::KeyParams& params = ndn::KeyChain::getDefaultKeyParams());
-
-  /**
-   *  @brief Save identity certificate to a file
-   *  @param identity identity
-   *  @param filename file name, should be writable
-   *  @return whether successful
-   */
-  bool
-  saveCertificate(const Identity& identity, const std::string& filename);
-
-  /**
-   * @brief Issue a certificate for \p subIdentityName signed by \p issuer
-   *
-   *  If identity does not exist, it is created.
-   *  A new key is generated as the default key for identity.
-   *  A default certificate for the key is signed by the issuer using its default certificate.
-   *
-   *  @return the sub identity
-   */
-  Identity
-  addSubCertificate(const Name& subIdentityName, const Identity& issuer,
-                    const ndn::KeyParams& params = ndn::KeyChain::getDefaultKeyParams());
-
-  /**
-   * @brief Add a self-signed certificate to @p key with issuer ID @p issuer
-   */
-  Certificate
-  addCertificate(const Key& key, const std::string& issuer);
-
-protected:
-  ndn::KeyChain m_keyChain;
-};
-
-} // namespace ndncert::tests
-
-#endif // NDN_TESTS_IDENTITY_MANAGEMENT_FIXTURE_HPP
diff --git a/tests/io-fixture.hpp b/tests/io-fixture.hpp
new file mode 100644
index 0000000..209026f
--- /dev/null
+++ b/tests/io-fixture.hpp
@@ -0,0 +1,53 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2022 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDNCERT_TESTS_IO_FIXTURE_HPP
+#define NDNCERT_TESTS_IO_FIXTURE_HPP
+
+#include "tests/clock-fixture.hpp"
+
+#include <boost/asio/io_service.hpp>
+
+namespace ndncert::tests {
+
+class IoFixture : public ClockFixture
+{
+private:
+  void
+  afterTick() final
+  {
+    if (m_io.stopped()) {
+#if BOOST_VERSION >= 106600
+      m_io.restart();
+#else
+      m_io.reset();
+#endif
+    }
+    m_io.poll();
+  }
+
+protected:
+  boost::asio::io_service m_io;
+};
+
+} // namespace ndncert::tests
+
+#endif // NDNCERT_TESTS_IO_FIXTURE_HPP
diff --git a/tests/io-key-chain-fixture.hpp b/tests/io-key-chain-fixture.hpp
new file mode 100644
index 0000000..36164b2
--- /dev/null
+++ b/tests/io-key-chain-fixture.hpp
@@ -0,0 +1,36 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2022 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDNCERT_TESTS_IO_KEY_CHAIN_FIXTURE_HPP
+#define NDNCERT_TESTS_IO_KEY_CHAIN_FIXTURE_HPP
+
+#include "tests/io-fixture.hpp"
+#include "tests/key-chain-fixture.hpp"
+
+namespace ndncert::tests {
+
+class IoKeyChainFixture : public IoFixture, public KeyChainFixture
+{
+};
+
+} // namespace ndncert::tests
+
+#endif // NDNCERT_TESTS_IO_KEY_CHAIN_FIXTURE_HPP
diff --git a/tests/key-chain-fixture.cpp b/tests/key-chain-fixture.cpp
new file mode 100644
index 0000000..5276ae4
--- /dev/null
+++ b/tests/key-chain-fixture.cpp
@@ -0,0 +1,93 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2022 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "tests/key-chain-fixture.hpp"
+
+#include <ndn-cxx/util/io.hpp>
+
+#include <boost/filesystem/operations.hpp>
+
+namespace ndncert::tests {
+
+using namespace ndn::security;
+
+KeyChainFixture::KeyChainFixture()
+  : m_keyChain("pib-memory:", "tpm-memory:")
+{
+}
+
+KeyChainFixture::~KeyChainFixture()
+{
+  boost::system::error_code ec;
+  for (const auto& certFile : m_certFiles) {
+    boost::filesystem::remove(certFile, ec); // ignore error
+  }
+}
+
+bool
+KeyChainFixture::saveCert(const Data& cert, const std::string& filename)
+{
+  m_certFiles.push_back(filename);
+  try {
+    ndn::io::save(cert, filename);
+    return true;
+  }
+  catch (const ndn::io::Error&) {
+    return false;
+  }
+}
+
+bool
+KeyChainFixture::saveIdentityCert(const Identity& identity, const std::string& filename)
+{
+  Certificate cert;
+  try {
+    cert = identity.getDefaultKey().getDefaultCertificate();
+  }
+  catch (const Pib::Error&) {
+    return false;
+  }
+
+  return saveCert(cert, filename);
+}
+
+bool
+KeyChainFixture::saveIdentityCert(const Name& identityName, const std::string& filename,
+                                  bool allowCreate)
+{
+  Identity id;
+  try {
+    id = m_keyChain.getPib().getIdentity(identityName);
+  }
+  catch (const Pib::Error&) {
+    if (allowCreate) {
+      id = m_keyChain.createIdentity(identityName);
+    }
+  }
+
+  if (!id) {
+    return false;
+  }
+
+  return saveIdentityCert(id, filename);
+}
+
+} // namespace ndncert::tests
diff --git a/tests/key-chain-fixture.hpp b/tests/key-chain-fixture.hpp
new file mode 100644
index 0000000..c785871
--- /dev/null
+++ b/tests/key-chain-fixture.hpp
@@ -0,0 +1,85 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2022 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDNCERT_TESTS_KEY_CHAIN_FIXTURE_HPP
+#define NDNCERT_TESTS_KEY_CHAIN_FIXTURE_HPP
+
+#include "detail/ndncert-common.hpp"
+
+#include <ndn-cxx/security/key-chain.hpp>
+#include <ndn-cxx/security/signing-helpers.hpp>
+
+namespace ndncert::tests {
+
+/**
+ * @brief A fixture providing an in-memory KeyChain.
+ *
+ * Test cases can use this fixture to create identities. Identities, certificates, and
+ * saved certificates are automatically removed during test teardown.
+ */
+class KeyChainFixture
+{
+protected:
+  using Certificate = ndn::security::Certificate;
+  using Identity    = ndn::security::Identity;
+  using Key         = ndn::security::Key;
+
+public:
+  /**
+   * @brief Saves an NDN certificate to a file
+   * @return true if successful, false otherwise
+   */
+  bool
+  saveCert(const Data& cert, const std::string& filename);
+
+  /**
+   * @brief Saves the default certificate of @p identity to a file
+   * @return true if successful, false otherwise
+   */
+  bool
+  saveIdentityCert(const Identity& identity, const std::string& filename);
+
+  /**
+   * @brief Saves the default certificate of the identity named @p identityName to a file
+   * @param identityName Name of the identity
+   * @param filename File name, must be writable
+   * @param allowCreate If true, create the identity if it does not exist
+   * @return true if successful, false otherwise
+   */
+  bool
+  saveIdentityCert(const Name& identityName, const std::string& filename,
+                   bool allowCreate = false);
+
+protected:
+  KeyChainFixture();
+
+  ~KeyChainFixture();
+
+protected:
+  ndn::KeyChain m_keyChain;
+
+private:
+  std::vector<std::string> m_certFiles;
+};
+
+} // namespace ndncert::tests
+
+#endif // NDNCERT_TESTS_KEY_CHAIN_FIXTURE_HPP
diff --git a/tests/test-common.hpp b/tests/test-common.hpp
deleted file mode 100644
index 68cccc2..0000000
--- a/tests/test-common.hpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Copyright (c) 2013-2020 Regents of the University of California.
- *
- * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
- *
- * ndn-cxx library is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Lesser General Public License as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
- *
- * You should have received copies of the GNU General Public License and GNU Lesser
- * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- */
-
-#ifndef NDNCERT_TESTS_TEST_COMMON_HPP
-#define NDNCERT_TESTS_TEST_COMMON_HPP
-
-#include "boost-test.hpp"
-#include "database-fixture.hpp"
-#include "identity-management-fixture.hpp"
-#include "unit-test-time-fixture.hpp"
-#include <ndn-cxx/metadata-object.hpp>
-#include <ndn-cxx/security/signing-helpers.hpp>
-#include <ndn-cxx/security/transform/base64-encode.hpp>
-#include <ndn-cxx/security/transform/buffer-source.hpp>
-#include <ndn-cxx/security/transform/public-key.hpp>
-#include <ndn-cxx/security/transform/stream-sink.hpp>
-#include <ndn-cxx/security/verification-helpers.hpp>
-#include <ndn-cxx/util/dummy-client-face.hpp>
-
-#endif // NDNCERT_TESTS_TEST_COMMON_HPP
diff --git a/tests/unit-test-time-fixture.hpp b/tests/unit-test-time-fixture.hpp
deleted file mode 100644
index 0ea3635..0000000
--- a/tests/unit-test-time-fixture.hpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/*
- * Copyright (c) 2013-2022 Regents of the University of California.
- *
- * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
- *
- * ndn-cxx library is free software: you can redistribute it and/or modify it under the
- * terms of the GNU Lesser General Public License as published by the Free Software
- * Foundation, either version 3 of the License, or (at your option) any later version.
- *
- * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
- *
- * You should have received copies of the GNU General Public License and GNU Lesser
- * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- */
-
-#ifndef NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP
-#define NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP
-
-#include "detail/ndncert-common.hpp"
-
-#include <ndn-cxx/util/time-unit-test-clock.hpp>
-
-#include <boost/asio/io_service.hpp>
-
-namespace ndncert::tests {
-
-/** \brief a test fixture that overrides steady clock and system clock
- */
-class UnitTestTimeFixture
-{
-public:
-  UnitTestTimeFixture()
-    : steadyClock(std::make_shared<time::UnitTestSteadyClock>())
-    , systemClock(std::make_shared<time::UnitTestSystemClock>())
-  {
-    time::setCustomClocks(steadyClock, systemClock);
-  }
-
-  ~UnitTestTimeFixture()
-  {
-    time::setCustomClocks(nullptr, nullptr);
-  }
-
-  /** \brief advance steady and system clocks
-   *
-   *  Clocks are advanced in increments of \p tick for \p nTicks ticks.
-   *  After each tick, io_service is polled to process pending I/O events.
-   *
-   *  Exceptions thrown during I/O events are propagated to the caller.
-   *  Clock advancing would stop in case of an exception.
-   */
-  void
-  advanceClocks(const time::nanoseconds& tick, size_t nTicks = 1)
-  {
-    this->advanceClocks(tick, tick * nTicks);
-  }
-
-  /** \brief advance steady and system clocks
-   *
-   *  Clocks are advanced in increments of \p tick for \p total time.
-   *  The last increment might be shorter than \p tick.
-   *  After each tick, io_service is polled to process pending I/O events.
-   *
-   *  Exceptions thrown during I/O events are propagated to the caller.
-   *  Clock advancing would stop in case of an exception.
-   */
-  void
-  advanceClocks(const time::nanoseconds& tick, const time::nanoseconds& total)
-  {
-    BOOST_ASSERT(tick > time::nanoseconds::zero());
-    BOOST_ASSERT(total >= time::nanoseconds::zero());
-
-    time::nanoseconds remaining = total;
-    while (remaining > time::nanoseconds::zero()) {
-      if (remaining >= tick) {
-        steadyClock->advance(tick);
-        systemClock->advance(tick);
-        remaining -= tick;
-      }
-      else {
-        steadyClock->advance(remaining);
-        systemClock->advance(remaining);
-        remaining = time::nanoseconds::zero();
-      }
-
-      if (io.stopped())
-        io.reset();
-      io.poll();
-    }
-  }
-
-public:
-  std::shared_ptr<time::UnitTestSteadyClock> steadyClock;
-  std::shared_ptr<time::UnitTestSystemClock> systemClock;
-  boost::asio::io_service io;
-};
-
-} // namespace ndncert::tests
-
-#endif // NDN_TESTS_UNIT_UNIT_TEST_TIME_FIXTURE_HPP
diff --git a/tests/unit-tests/bench.t.cpp b/tests/unit-tests/bench.t.cpp
index 335f448..2a97b2c 100644
--- a/tests/unit-tests/bench.t.cpp
+++ b/tests/unit-tests/bench.t.cpp
@@ -23,19 +23,24 @@
 #include "detail/info-encoder.hpp"
 #include "requester-request.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/io-key-chain-fixture.hpp"
+
+#include <ndn-cxx/metadata-object.hpp>
+#include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
 
 namespace ndncert::tests {
 
-BOOST_FIXTURE_TEST_SUITE(Benchmark, IdentityManagementTimeFixture)
+BOOST_FIXTURE_TEST_SUITE(Benchmark, IoKeyChainFixture)
 
 BOOST_AUTO_TEST_CASE(PacketSize0)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  ndn::util::DummyClientFace face(io, m_keyChain, {true, true});
+  ndn::util::DummyClientFace face(m_io, m_keyChain, {true, true});
   ca::CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
   auto profileData = ca.getCaProfileData();
@@ -89,11 +94,11 @@
 
 BOOST_AUTO_TEST_CASE(PacketSize1)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  ndn::util::DummyClientFace face(io, m_keyChain, {true, true});
+  ndn::util::DummyClientFace face(m_io, m_keyChain, {true, true});
   ca::CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -102,7 +107,7 @@
   item.caPrefix = Name("/ndn");
   item.cert = std::make_shared<Certificate>(cert);
   requester::Request state(m_keyChain, item, RequestType::NEW);
-  auto newInterest = state.genNewInterest(addIdentity(Name("/ndn/alice")).getDefaultKey().getName(),
+  auto newInterest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/alice")).getDefaultKey().getName(),
                                           time::system_clock::now(),
                                           time::system_clock::now() + time::days(1));
   // std::cout << "New Interest Size: " << newInterest->wireEncode().size() << std::endl;
diff --git a/tests/unit-tests/ca-memory.t.cpp b/tests/unit-tests/ca-memory.t.cpp
index fa1ddd8..1a15713 100644
--- a/tests/unit-tests/ca-memory.t.cpp
+++ b/tests/unit-tests/ca-memory.t.cpp
@@ -20,19 +20,20 @@
 
 #include "detail/ca-memory.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/key-chain-fixture.hpp"
 
 namespace ndncert::tests {
 
 using namespace ca;
 
-BOOST_FIXTURE_TEST_SUITE(TestCaMemory, IdentityManagementFixture)
+BOOST_FIXTURE_TEST_SUITE(TestCaMemory, KeyChainFixture)
 
 BOOST_AUTO_TEST_CASE(RequestOperations)
 {
   CaMemory storage;
 
-  auto identity1 = addIdentity(Name("/ndn/site1"));
+  auto identity1 = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key1 = identity1.getDefaultKey();
   auto cert1 = key1.getDefaultCertificate();
 
@@ -71,7 +72,7 @@
   BOOST_CHECK_EQUAL(request2.caPrefix, result.caPrefix);
 
   // another add operation
-  auto identity2 = addIdentity(Name("/ndn/site2"));
+  auto identity2 = m_keyChain.createIdentity(Name("/ndn/site2"));
   auto key2 = identity2.getDefaultKey();
   auto cert2 = key2.getDefaultCertificate();
   RequestId requestId2 = {{102}};
diff --git a/tests/unit-tests/ca-module.t.cpp b/tests/unit-tests/ca-module.t.cpp
index 19f369d..b0d4955 100644
--- a/tests/unit-tests/ca-module.t.cpp
+++ b/tests/unit-tests/ca-module.t.cpp
@@ -25,7 +25,12 @@
 #include "detail/info-encoder.hpp"
 #include "requester-request.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/io-key-chain-fixture.hpp"
+
+#include <ndn-cxx/metadata-object.hpp>
+#include <ndn-cxx/security/verification-helpers.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
 
 namespace ndncert::tests {
 
@@ -33,11 +38,11 @@
 using ndn::util::DummyClientFace;
 using ndn::security::verifySignature;
 
-BOOST_FIXTURE_TEST_SUITE(TestCaModule, DatabaseFixture)
+BOOST_FIXTURE_TEST_SUITE(TestCaModule, IoKeyChainFixture)
 
 BOOST_AUTO_TEST_CASE(Initialization)
 {
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   BOOST_CHECK_EQUAL(ca.getCaConf().caProfile.caPrefix, "/ndn");
 
@@ -48,11 +53,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleProfileFetching)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
   auto profileData = ca.getCaProfileData();
@@ -101,11 +106,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleProbe)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -136,11 +141,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleProbeUsingDefaultHandler)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -171,11 +176,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleProbeRedirection)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-5", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -211,11 +216,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleNew)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -223,7 +228,7 @@
   item.caPrefix = Name("/ndn");
   item.cert = std::make_shared<Certificate>(cert);
   requester::Request state(m_keyChain, item, RequestType::NEW);
-  auto interest = state.genNewInterest(addIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(),
+  auto interest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(),
                                        time::system_clock::now(),
                                        time::system_clock::now() + time::days(1));
 
@@ -262,11 +267,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleNewWithInvalidValidityPeriod1)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -274,7 +279,7 @@
   item.caPrefix = Name("/ndn");
   item.cert = std::make_shared<Certificate>(cert);
   requester::Request state(m_keyChain, item, RequestType::NEW);
-  auto client = addIdentity(Name("/ndn/zhiyi"));
+  auto client = m_keyChain.createIdentity(Name("/ndn/zhiyi"));
   auto current_tp = time::system_clock::now();
   auto interest1 = state.genNewInterest(client.getDefaultKey().getName(), current_tp, current_tp - time::hours(1));
   auto interest2 = state.genNewInterest(client.getDefaultKey().getName(), current_tp, current_tp + time::days(361));
@@ -295,7 +300,7 @@
 
 BOOST_AUTO_TEST_CASE(HandleNewWithServerBadValidity)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
 
   //build expired cert
@@ -304,12 +309,11 @@
   cert.setContentType(ndn::tlv::ContentType_Key);
   cert.setContent(key.getPublicKey());
   SignatureInfo signatureInfo;
-  signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod(time::system_clock::now() - time::days(1),
-                                                                time::system_clock::now() - time::seconds(1)));
+  signatureInfo.setValidityPeriod(ndn::security::ValidityPeriod::makeRelative(-1_days, -1_s));
   m_keyChain.sign(cert, signingByKey(key.getName()).setSignatureInfo(signatureInfo));
   m_keyChain.setDefaultCertificate(key, cert);
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -317,7 +321,7 @@
   item.caPrefix = Name("/ndn");
   item.cert = std::make_shared<Certificate>(cert);
   requester::Request state(m_keyChain, item, RequestType::NEW);
-  auto interest = state.genNewInterest(addIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(),
+  auto interest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(),
                                        time::system_clock::now(),
                                        time::system_clock::now() + time::days(1));
 
@@ -337,11 +341,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleNewWithLongSuffix)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -350,12 +354,15 @@
   item.cert = std::make_shared<Certificate>(cert);
   requester::Request state(m_keyChain, item, RequestType::NEW);
 
-  auto interest1 = state.genNewInterest(addIdentity(Name("/ndn/a")).getDefaultKey().getName(), time::system_clock::now(),
-                                              time::system_clock::now() + time::days(1));
-  auto interest2 = state.genNewInterest(addIdentity(Name("/ndn/a/b")).getDefaultKey().getName(), time::system_clock::now(),
-                                              time::system_clock::now() + time::days(1));
-  auto interest3 = state.genNewInterest(addIdentity(Name("/ndn/a/b/c/d")).getDefaultKey().getName(), time::system_clock::now(),
-                                              time::system_clock::now() + time::days(1));
+  auto interest1 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a")).getDefaultKey().getName(),
+                                        time::system_clock::now(),
+                                        time::system_clock::now() + time::days(1));
+  auto interest2 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a/b")).getDefaultKey().getName(),
+                                        time::system_clock::now(),
+                                        time::system_clock::now() + time::days(1));
+  auto interest3 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a/b/c/d")).getDefaultKey().getName(),
+                                        time::system_clock::now(),
+                                        time::system_clock::now() + time::days(1));
 
   face.onSendData.connect([&](const Data& response) {
     auto contentTlv = response.getContent();
@@ -377,11 +384,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleNewWithInvalidLength1)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -392,7 +399,7 @@
 
   auto current_tp = time::system_clock::now();
   auto interest1 = state.genNewInterest(identity.getDefaultKey().getName(), current_tp, current_tp + time::days(1));
-  auto interest2 = state.genNewInterest(addIdentity(Name("/ndn/a/b/c/d")).getDefaultKey().getName(),
+  auto interest2 = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/a/b/c/d")).getDefaultKey().getName(),
                                         current_tp, current_tp + time::days(1));
   face.onSendData.connect([&](const Data& response) {
     auto contentTlv = response.getContent();
@@ -408,11 +415,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleChallenge)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, m_keyChain, {true, true});
+  DummyClientFace face(m_io, m_keyChain, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -422,8 +429,9 @@
   item.cert = std::make_shared<Certificate>(cert);
   requester::Request state(m_keyChain, item, RequestType::NEW);
 
-  auto newInterest = state.genNewInterest(addIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(), time::system_clock::now(),
-                                                time::system_clock::now() + time::days(1));
+  auto newInterest = state.genNewInterest(m_keyChain.createIdentity(Name("/ndn/zhiyi")).getDefaultKey().getName(),
+                                          time::system_clock::now(),
+                                          time::system_clock::now() + time::days(1));
 
   // generate CHALLENGE Interest
   std::shared_ptr<Interest> challengeInterest;
@@ -482,11 +490,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleRevoke)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, {true, true});
+  DummyClientFace face(m_io, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
@@ -553,11 +561,11 @@
 
 BOOST_AUTO_TEST_CASE(HandleRevokeWithBadCert)
 {
-  auto identity = addIdentity(Name("/ndn"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
-  DummyClientFace face(io, {true, true});
+  DummyClientFace face(m_io, {true, true});
   CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-1", "ca-storage-memory");
   advanceClocks(time::milliseconds(20), 60);
 
diff --git a/tests/unit-tests/ca-sqlite.t.cpp b/tests/unit-tests/ca-sqlite.t.cpp
index fcd1348..416c32c 100644
--- a/tests/unit-tests/ca-sqlite.t.cpp
+++ b/tests/unit-tests/ca-sqlite.t.cpp
@@ -20,19 +20,44 @@
 
 #include "detail/ca-sqlite.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/key-chain-fixture.hpp"
+
+#include <boost/filesystem/operations.hpp>
+#include <boost/filesystem/path.hpp>
 
 namespace ndncert::tests {
 
 using namespace ca;
 
+class DatabaseFixture : public KeyChainFixture
+{
+public:
+  DatabaseFixture()
+  {
+    boost::filesystem::path parentDir{UNIT_TESTS_TMPDIR};
+    dbDir = parentDir / "test-home" / ".ndncert";
+    if (!boost::filesystem::exists(dbDir)) {
+      boost::filesystem::create_directory(dbDir);
+    }
+  }
+
+  ~DatabaseFixture()
+  {
+    boost::filesystem::remove_all(dbDir);
+  }
+
+protected:
+  boost::filesystem::path dbDir;
+};
+
 BOOST_FIXTURE_TEST_SUITE(TestCaSqlite, DatabaseFixture)
 
 BOOST_AUTO_TEST_CASE(RequestOperations)
 {
   CaSqlite storage(Name(), dbDir.string() + "/TestCaSqlite_RequestOperations.db");
 
-  auto identity1 = addIdentity(Name("/ndn/site1"));
+  auto identity1 = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key1 = identity1.getDefaultKey();
   auto cert1 = key1.getDefaultCertificate();
 
@@ -84,7 +109,7 @@
                                 result.decryptionIv.begin(), result.decryptionIv.end());
 
   // another add operation
-  auto identity2 = addIdentity(Name("/ndn/site2"));
+  auto identity2 = m_keyChain.createIdentity(Name("/ndn/site2"));
   auto key2 = identity2.getDefaultKey();
   auto cert2 = key2.getDefaultCertificate();
   RequestId requestId2 = {{102}};
@@ -112,7 +137,7 @@
 {
   CaSqlite storage(Name(), dbDir.string() + "/TestCaSqlite_DuplicateAdd.db");
 
-  auto identity1 = addIdentity(Name("/ndn/site1"));
+  auto identity1 = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key1 = identity1.getDefaultKey();
   auto cert1 = key1.getDefaultCertificate();
 
diff --git a/tests/unit-tests/challenge-email.t.cpp b/tests/unit-tests/challenge-email.t.cpp
index d996d4a..61bcd45 100644
--- a/tests/unit-tests/challenge-email.t.cpp
+++ b/tests/unit-tests/challenge-email.t.cpp
@@ -20,11 +20,14 @@
 
 #include "challenge/challenge-email.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/key-chain-fixture.hpp"
+
+#include <fstream>
 
 namespace ndncert::tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestChallengeEmail, IdentityManagementFixture)
+BOOST_FIXTURE_TEST_SUITE(TestChallengeEmail, KeyChainFixture)
 
 BOOST_AUTO_TEST_CASE(ChallengeType)
 {
@@ -41,7 +44,7 @@
 
 BOOST_AUTO_TEST_CASE(OnChallengeRequestWithEmail)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
   RequestId requestId = {{101}};
@@ -95,7 +98,7 @@
 
 BOOST_AUTO_TEST_CASE(OnChallengeRequestWithCode)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
   JsonSection secret;
@@ -124,7 +127,7 @@
 
 BOOST_AUTO_TEST_CASE(OnValidateInterestComingWithWrongCode)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
   JsonSection secret;
diff --git a/tests/unit-tests/challenge-pin.t.cpp b/tests/unit-tests/challenge-pin.t.cpp
index 49e3e29..dc5b981 100644
--- a/tests/unit-tests/challenge-pin.t.cpp
+++ b/tests/unit-tests/challenge-pin.t.cpp
@@ -20,11 +20,12 @@
 
 #include "challenge/challenge-pin.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/key-chain-fixture.hpp"
 
 namespace ndncert::tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestChallengePin, IdentityManagementFixture)
+BOOST_FIXTURE_TEST_SUITE(TestChallengePin, KeyChainFixture)
 
 BOOST_AUTO_TEST_CASE(ChallengeType)
 {
@@ -34,7 +35,7 @@
 
 BOOST_AUTO_TEST_CASE(OnChallengeRequestWithEmptyInfo)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
   RequestId requestId = {{101}};
@@ -54,7 +55,7 @@
 
 BOOST_AUTO_TEST_CASE(OnChallengeRequestWithCode)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
   JsonSection secret;
@@ -83,7 +84,7 @@
 
 BOOST_AUTO_TEST_CASE(OnChallengeRequestWithWrongCode)
 {
-  auto identity = addIdentity(Name("/ndn/site1"));
+  auto identity = m_keyChain.createIdentity(Name("/ndn/site1"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
   JsonSection secret;
diff --git a/tests/unit-tests/challenge-possession.t.cpp b/tests/unit-tests/challenge-possession.t.cpp
index 5294ff0..cc694e6 100644
--- a/tests/unit-tests/challenge-possession.t.cpp
+++ b/tests/unit-tests/challenge-possession.t.cpp
@@ -21,17 +21,18 @@
 #include "challenge/challenge-possession.hpp"
 #include "detail/challenge-encoder.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/key-chain-fixture.hpp"
 
 namespace ndncert::tests {
 
-class ChallengePossessionFixture : public IdentityManagementFixture
+class ChallengePossessionFixture : public KeyChainFixture
 {
 public:
   void
   createTrustAnchor()
   {
-    trustAnchor = addIdentity("/trust").getDefaultKey().getDefaultCertificate();
+    trustAnchor = m_keyChain.createIdentity("/trust").getDefaultKey().getDefaultCertificate();
     challenge.parseConfigFile();
     challenge.m_trustAnchors.front() = trustAnchor;
   }
@@ -42,13 +43,13 @@
     state.caPrefix = "/example";
     state.requestId = RequestId{{101}};
     state.requestType = RequestType::NEW;
-    state.cert = addIdentity("/example").getDefaultKey().getDefaultCertificate();
+    state.cert = m_keyChain.createIdentity("/example").getDefaultKey().getDefaultCertificate();
   }
 
   void
   createRequesterCredential()
   {
-    auto keyB = addIdentity("/trust/cert").getDefaultKey();
+    auto keyB = m_keyChain.createIdentity("/trust/cert").getDefaultKey();
     ndn::security::MakeCertificateOptions opts;
     opts.issuerId = ndn::name::Component("Credential");
     opts.validity.emplace(ndn::security::ValidityPeriod::makeRelative(-1_s, 1_min));
diff --git a/tests/unit-tests/configuration.t.cpp b/tests/unit-tests/configuration.t.cpp
index 7d38adc..ec43b17 100644
--- a/tests/unit-tests/configuration.t.cpp
+++ b/tests/unit-tests/configuration.t.cpp
@@ -22,11 +22,11 @@
 #include "detail/profile-storage.hpp"
 #include "detail/info-encoder.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
 
 namespace ndncert::tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestConfig, IdentityManagementFixture)
+BOOST_AUTO_TEST_SUITE(TestConfig)
 
 BOOST_AUTO_TEST_CASE(CaConfigFile)
 {
diff --git a/tests/unit-tests/crypto-helpers.t.cpp b/tests/unit-tests/crypto-helpers.t.cpp
index e67828f..ff645dd 100644
--- a/tests/unit-tests/crypto-helpers.t.cpp
+++ b/tests/unit-tests/crypto-helpers.t.cpp
@@ -20,7 +20,7 @@
 
 #include "detail/crypto-helpers.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
 
 namespace ndncert::tests {
 
diff --git a/tests/main.cpp b/tests/unit-tests/main.cpp
similarity index 93%
rename from tests/main.cpp
rename to tests/unit-tests/main.cpp
index 779d74a..c67a050 100644
--- a/tests/main.cpp
+++ b/tests/unit-tests/main.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020, Regents of the University of California,
+ * Copyright (c) 2014-2022, Regents of the University of California,
  *                          Arizona Board of Regents,
  *                          Colorado State University,
  *                          University Pierre & Marie Curie, Sorbonne University,
@@ -26,4 +26,4 @@
  */
 
 #define BOOST_TEST_MODULE ndncert
-#include "boost-test.hpp"
\ No newline at end of file
+#include "tests/boost-test.hpp"
diff --git a/tests/unit-tests/name-assignment.t.cpp b/tests/unit-tests/name-assignment.t.cpp
index da8396a..868e788 100644
--- a/tests/unit-tests/name-assignment.t.cpp
+++ b/tests/unit-tests/name-assignment.t.cpp
@@ -23,7 +23,7 @@
 #include "name-assignment/assignment-param.hpp"
 #include "name-assignment/assignment-hash.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
 
 namespace ndncert::tests {
 
diff --git a/tests/unit-tests/protocol-encoders.t.cpp b/tests/unit-tests/protocol-encoders.t.cpp
index a569f0e..4d7e6c8 100644
--- a/tests/unit-tests/protocol-encoders.t.cpp
+++ b/tests/unit-tests/protocol-encoders.t.cpp
@@ -25,11 +25,13 @@
 #include "detail/request-encoder.hpp"
 #include "detail/ca-configuration.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/clock-fixture.hpp"
+#include "tests/key-chain-fixture.hpp"
 
 namespace ndncert::tests {
 
-BOOST_FIXTURE_TEST_SUITE(TestProtocolEncoding, IdentityManagementTimeFixture)
+BOOST_AUTO_TEST_SUITE(TestProtocolEncoding)
 
 BOOST_AUTO_TEST_CASE(InfoEncoding)
 {
@@ -111,11 +113,7 @@
   std::shared_ptr<Certificate> returnedCert;
   requesttlv::decodeApplicationParameters(b, RequestType::REVOKE, returnedPub, returnedCert);
 
-  BOOST_CHECK_EQUAL(returnedPub.size(), pub.size());
-  for (auto it1 = returnedPub.begin(), it2 = pub.begin();
-       it1 != returnedPub.end() && it2 != pub.end(); it1++, it2++) {
-    BOOST_CHECK_EQUAL(*it1, *it2);
-  }
+  BOOST_TEST(returnedPub == pub, boost::test_tools::per_element());
   BOOST_CHECK_EQUAL(*returnedCert, *certRequest);
 }
 
@@ -137,7 +135,11 @@
   BOOST_CHECK_EQUAL_COLLECTIONS(returnedId.begin(), returnedId.end(), id.begin(), id.end());
 }
 
-BOOST_AUTO_TEST_CASE(ChallengeEncoding)
+class ChallengeEncodingFixture : public ClockFixture, public KeyChainFixture
+{
+};
+
+BOOST_FIXTURE_TEST_CASE(ChallengeEncoding, ChallengeEncodingFixture)
 {
   const uint8_t key[] = {0x23, 0x70, 0xe3, 0x20, 0xd4, 0x34, 0x42, 0x08,
                          0xe0, 0xff, 0x56, 0x83, 0xf2, 0x43, 0xb2, 0x13};
diff --git a/tests/unit-tests/redirection-policy.t.cpp b/tests/unit-tests/redirection-policy.t.cpp
index bcc0d9f..f671d46 100644
--- a/tests/unit-tests/redirection-policy.t.cpp
+++ b/tests/unit-tests/redirection-policy.t.cpp
@@ -22,7 +22,7 @@
 #include "redirection/redirection-param.hpp"
 #include "redirection/redirection-email.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
 
 namespace ndncert::tests {
 
diff --git a/tests/unit-tests/requester.t.cpp b/tests/unit-tests/requester.t.cpp
index 858a127..d1a47a4 100644
--- a/tests/unit-tests/requester.t.cpp
+++ b/tests/unit-tests/requester.t.cpp
@@ -24,17 +24,20 @@
 #include "detail/error-encoder.hpp"
 #include "detail/probe-encoder.hpp"
 
-#include "test-common.hpp"
+#include "tests/boost-test.hpp"
+#include "tests/io-key-chain-fixture.hpp"
+
+#include <ndn-cxx/util/dummy-client-face.hpp>
 
 namespace ndncert::tests {
 
 using namespace requester;
 
-BOOST_FIXTURE_TEST_SUITE(TestRequester, IdentityManagementTimeFixture)
+BOOST_FIXTURE_TEST_SUITE(TestRequester, IoKeyChainFixture)
 
 BOOST_AUTO_TEST_CASE(GenProbeInterest)
 {
-  auto identity = addIdentity(Name("/site"));
+  auto identity = m_keyChain.createIdentity(Name("/site"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
@@ -57,8 +60,9 @@
   BOOST_CHECK_EQUAL(readString(firstInterest->getApplicationParameters().get(tlv::ParameterValue)), "zhiyi@cs.ucla.edu");
 }
 
-BOOST_AUTO_TEST_CASE(OnProbeResponse){
-  auto identity = addIdentity(Name("/site"));
+BOOST_AUTO_TEST_CASE(OnProbeResponse)
+{
+  auto identity = m_keyChain.createIdentity(Name("/site"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
@@ -73,7 +77,7 @@
   availableNames.emplace_back("/site1");
   availableNames.emplace_back("/site2");
 
-  ndn::util::DummyClientFace face(io, m_keyChain, {true, true});
+  ndn::util::DummyClientFace face(m_io, m_keyChain, {true, true});
   ca::CaModule ca(face, m_keyChain, "tests/unit-tests/config-files/config-ca-5", "ca-storage-memory");
 
   Data reply;
@@ -104,7 +108,7 @@
 
 BOOST_AUTO_TEST_CASE(ErrorHandling)
 {
-  auto identity = addIdentity(Name("/site"));
+  auto identity = m_keyChain.createIdentity(Name("/site"));
   auto key = identity.getDefaultKey();
   auto cert = key.getDefaultCertificate();
 
diff --git a/tests/wscript b/tests/wscript
index 873fb4a..89ac662 100644
--- a/tests/wscript
+++ b/tests/wscript
@@ -5,12 +5,11 @@
     if not bld.env.WITH_TESTS:
         return
 
-    tmpdir = 'TMP_TESTS_PATH="%s"' % bld.bldnode.make_node('tmp-files')
+    tmpdir = 'UNIT_TESTS_TMPDIR="%s"' % bld.bldnode.make_node('tmp-files')
     bld.program(
         target='../unit-tests',
         name='unit-tests',
         source=bld.path.ant_glob(['*.cpp', 'unit-tests/**/*.cpp']),
         use='ndn-cert',
-        includes='.',
         defines=[tmpdir],
         install_path=None)