tests: test IP transports under various combos of address family/scope

In particular, link-local addresses are now tested as well.

Additionally, WebSocketTransport send/receive tests are extended
to check transport counters.

Change-Id: I5b35f498a5d8d70cb1f7a2bea3ce99d22370e050
Refs: #1428
diff --git a/tests/daemon/face/datagram-transport.t.cpp b/tests/daemon/face/datagram-transport.t.cpp
index d6286eb..6121152 100644
--- a/tests/daemon/face/datagram-transport.t.cpp
+++ b/tests/daemon/face/datagram-transport.t.cpp
@@ -26,6 +26,8 @@
 #include "unicast-udp-transport-fixture.hpp"
 #include "multicast-udp-transport-fixture.hpp"
 
+#include "transport-test-common.hpp"
+
 #include <boost/mpl/vector.hpp>
 
 namespace nfd {
@@ -35,14 +37,14 @@
 BOOST_AUTO_TEST_SUITE(Face)
 BOOST_AUTO_TEST_SUITE(TestDatagramTransport)
 
-typedef boost::mpl::vector<UnicastUdpTransportFixture,
-                           MulticastUdpTransportFixture
-                           > DatagramTransportFixtures;
+using DatagramTransportFixtures = boost::mpl::vector<
+  GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(UnicastUdpTransportFixture),
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4, AddressScope::Global, MulticastInterface::Yes>
+>;
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(Send, T, DatagramTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(this->defaultAddr);
-  this->initialize(this->defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   auto block1 = ndn::encoding::makeStringBlock(300, "hello");
   this->transport->send(Transport::Packet{Block{block1}}); // make a copy of the block
@@ -58,8 +60,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveNormal, T, DatagramTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(this->defaultAddr);
-  this->initialize(this->defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   Block pkt = ndn::encoding::makeStringBlock(300, "hello");
   ndn::Buffer buf(pkt.begin(), pkt.end());
@@ -73,8 +74,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveIncomplete, T, DatagramTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(this->defaultAddr);
-  this->initialize(this->defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   this->remoteWrite({0x05, 0x03, 0x00, 0x01});
 
@@ -86,8 +86,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveTrailingGarbage, T, DatagramTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(this->defaultAddr);
-  this->initialize(this->defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
   Block pkt2 = ndn::encoding::makeStringBlock(301, "world");
@@ -105,8 +104,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveTooLarge, T, DatagramTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(this->defaultAddr);
-  this->initialize(this->defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   std::vector<uint8_t> bytes(ndn::MAX_NDN_PACKET_SIZE, 0);
   Block pkt1 = ndn::encoding::makeBinaryBlock(300, bytes.data(), bytes.size() - 6);
@@ -134,8 +132,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(Close, T, DatagramTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(this->defaultAddr);
-  this->initialize(this->defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   this->transport->afterStateChange.connectSingleShot([] (TransportState oldState, TransportState newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::UP);
diff --git a/tests/daemon/face/ethernet-fixture.hpp b/tests/daemon/face/ethernet-fixture.hpp
index 334fe21..60402a6 100644
--- a/tests/daemon/face/ethernet-fixture.hpp
+++ b/tests/daemon/face/ethernet-fixture.hpp
@@ -30,7 +30,7 @@
 #include "face/unicast-ethernet-transport.hpp"
 
 #include "tests/limited-io.hpp"
-#include "test-netif-ip.hpp"
+#include "test-netif.hpp"
 
 namespace nfd {
 namespace face {
diff --git a/tests/daemon/face/face-system-fixture.hpp b/tests/daemon/face/face-system-fixture.hpp
index 530b8e1..1d69179 100644
--- a/tests/daemon/face/face-system-fixture.hpp
+++ b/tests/daemon/face/face-system-fixture.hpp
@@ -32,7 +32,7 @@
 #include "fw/face-table.hpp"
 
 #include "tests/test-common.hpp"
-#include "test-netif-ip.hpp"
+#include "test-netif.hpp"
 #include <ndn-cxx/net/network-monitor-stub.hpp>
 
 namespace nfd {
diff --git a/tests/daemon/face/multicast-udp-transport-fixture.hpp b/tests/daemon/face/multicast-udp-transport-fixture.hpp
index c2b92b2..7c3ca21 100644
--- a/tests/daemon/face/multicast-udp-transport-fixture.hpp
+++ b/tests/daemon/face/multicast-udp-transport-fixture.hpp
@@ -30,7 +30,6 @@
 #include "face/face.hpp"
 
 #include "dummy-receive-link-service.hpp"
-#include "test-netif-ip.hpp"
 #include "tests/limited-io.hpp"
 
 namespace nfd {
@@ -47,7 +46,6 @@
   MulticastUdpTransportFixture()
     : transport(nullptr)
     , multicastEp(ip::address::from_string("230.15.19.47"), 7070)
-    , defaultAddr(getTestIp(AddressFamily::V4, AddressScope::Global, MulticastInterface::Yes))
     , receivedPackets(nullptr)
     , remoteSockRx(g_io)
     , remoteSockTx(g_io)
@@ -63,12 +61,11 @@
     udp::socket sockTx(g_io);
     localEp = udp::endpoint(address, 7001);
     openMulticastSockets(sockRx, sockTx, localEp.port());
-    ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS;
 
     face = make_unique<Face>(
              make_unique<DummyReceiveLinkService>(),
              make_unique<MulticastUdpTransport>(localEp, multicastEp, std::move(sockRx),
-                                                std::move(sockTx), linkType));
+                                                std::move(sockTx), ndn::nfd::LINK_TYPE_MULTI_ACCESS));
     transport = static_cast<MulticastUdpTransport*>(face->getTransport());
     receivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
 
@@ -119,7 +116,6 @@
   MulticastUdpTransport* transport;
   udp::endpoint localEp;
   udp::endpoint multicastEp;
-  const ip::address defaultAddr;
   std::vector<Transport::Packet>* receivedPackets;
 
 private:
diff --git a/tests/daemon/face/multicast-udp-transport.t.cpp b/tests/daemon/face/multicast-udp-transport.t.cpp
index 2b9407d..6e4a8cf 100644
--- a/tests/daemon/face/multicast-udp-transport.t.cpp
+++ b/tests/daemon/face/multicast-udp-transport.t.cpp
@@ -1,5 +1,5 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
  * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
@@ -27,47 +27,58 @@
 
 #include "multicast-udp-transport-fixture.hpp"
 
+#include <boost/mpl/vector.hpp>
+
 namespace nfd {
 namespace face {
 namespace tests {
 
 BOOST_AUTO_TEST_SUITE(Face)
-BOOST_FIXTURE_TEST_SUITE(TestMulticastUdpTransport, MulticastUdpTransportFixture)
 
-BOOST_AUTO_TEST_CASE(StaticPropertiesNonLocalIpv4)
+using MulticastUdpTransportFixtureWithAddress =
+  // TODO: change to AddressFamily::Any after IPv6 support is implemented
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4,
+                     AddressScope::Global, MulticastInterface::Yes>;
+
+BOOST_FIXTURE_TEST_SUITE(TestMulticastUdpTransport, MulticastUdpTransportFixtureWithAddress)
+
+using MulticastUdpTransportFixtures = boost::mpl::vector<
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4, AddressScope::Global, MulticastInterface::Yes>
+  // TODO: IPv6 not supported yet
+  //IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::LinkLocal, MulticastInterface::Yes>,
+  //IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::Global, MulticastInterface::Yes>
+>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticProperties, T, MulticastUdpTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(defaultAddr);
-  initialize(defaultAddr);
+  TRANSPORT_TEST_INIT();
 
-  checkStaticPropertiesInitialized(*transport);
+  checkStaticPropertiesInitialized(*this->transport);
 
-  BOOST_CHECK_EQUAL(transport->getLocalUri(),
-                    FaceUri("udp4://" + defaultAddr.to_string() + ":" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(),
-                    FaceUri("udp4://" + multicastEp.address().to_string() + ":" + to_string(multicastEp.port())));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_MULTI_ACCESS);
-  BOOST_CHECK_EQUAL(transport->getMtu(), 65535 - 60 - 8);
+  BOOST_CHECK_EQUAL(this->transport->getLocalUri(), FaceUri(udp::endpoint(this->address, this->localEp.port())));
+  BOOST_CHECK_EQUAL(this->transport->getRemoteUri(), FaceUri(this->multicastEp));
+  BOOST_CHECK_EQUAL(this->transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_MULTI_ACCESS);
+  BOOST_CHECK_EQUAL(this->transport->getMtu(),
+                    this->addressFamily == AddressFamily::V4 ? (65535 - 60 - 8) : (65535 - 8));
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
 {
-  SKIP_IF_IP_UNAVAILABLE(defaultAddr);
-  initialize(defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND), false);
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERSISTENT), false);
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERMANENT), true);
 }
 
-BOOST_AUTO_TEST_CASE(ReceiveMultipleRemoteEndpoints)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveMultipleRemoteEndpoints, T, MulticastUdpTransportFixtures, T)
 {
-  SKIP_IF_IP_UNAVAILABLE(defaultAddr);
-  initialize(defaultAddr);
+  TRANSPORT_TEST_INIT();
 
   // remoteSockRx2 unnecessary for this test case - only remoteSockTx2 is needed
-  udp::socket remoteSockTx2(g_io);
+  udp::socket remoteSockTx2(this->g_io);
   remoteSockTx2.open(udp::v4());
   remoteSockTx2.set_option(udp::socket::reuse_address(true));
   remoteSockTx2.set_option(ip::multicast::enable_loopback(true));
@@ -75,21 +86,21 @@
 
   Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
   ndn::Buffer buf1(pkt1.begin(), pkt1.end());
-  remoteWrite(buf1);
+  this->remoteWrite(buf1);
 
   Block pkt2 = ndn::encoding::makeStringBlock(301, "world");
   ndn::Buffer buf2(pkt2.begin(), pkt2.end());
-  remoteWrite(buf2);
+  this->remoteWrite(buf2);
 
-  BOOST_CHECK_EQUAL(transport->getCounters().nInPackets, 2);
-  BOOST_CHECK_EQUAL(transport->getCounters().nInBytes, buf1.size() + buf2.size());
-  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInPackets, 2);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInBytes, buf1.size() + buf2.size());
+  BOOST_CHECK_EQUAL(this->transport->getState(), TransportState::UP);
 
-  BOOST_REQUIRE_EQUAL(receivedPackets->size(), 2);
-  BOOST_CHECK_EQUAL(receivedPackets->at(0).remoteEndpoint,
-                    receivedPackets->at(1).remoteEndpoint);
+  BOOST_REQUIRE_EQUAL(this->receivedPackets->size(), 2);
+  BOOST_CHECK_EQUAL(this->receivedPackets->at(0).remoteEndpoint,
+                    this->receivedPackets->at(1).remoteEndpoint);
 
-  udp::endpoint destEp(multicastEp.address(), localEp.port());
+  udp::endpoint destEp(this->multicastEp.address(), this->localEp.port());
   remoteSockTx2.async_send_to(boost::asio::buffer(buf1), destEp,
     [] (const boost::system::error_code& error, size_t) {
       BOOST_REQUIRE_EQUAL(error, boost::system::errc::success);
@@ -98,17 +109,17 @@
     [] (const boost::system::error_code& error, size_t) {
       BOOST_REQUIRE_EQUAL(error, boost::system::errc::success);
     });
-  limitedIo.defer(time::seconds(1));
+  this->limitedIo.defer(time::seconds(1));
 
-  BOOST_CHECK_EQUAL(transport->getCounters().nInPackets, 4);
-  BOOST_CHECK_EQUAL(transport->getCounters().nInBytes, 2 * buf1.size() + 2 * buf2.size());
-  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInPackets, 4);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInBytes, 2 * buf1.size() + 2 * buf2.size());
+  BOOST_CHECK_EQUAL(this->transport->getState(), TransportState::UP);
 
-  BOOST_REQUIRE_EQUAL(receivedPackets->size(), 4);
-  BOOST_CHECK_EQUAL(receivedPackets->at(2).remoteEndpoint,
-                    receivedPackets->at(3).remoteEndpoint);
-  BOOST_CHECK_NE(receivedPackets->at(0).remoteEndpoint,
-                 receivedPackets->at(2).remoteEndpoint);
+  BOOST_REQUIRE_EQUAL(this->receivedPackets->size(), 4);
+  BOOST_CHECK_EQUAL(this->receivedPackets->at(2).remoteEndpoint,
+                    this->receivedPackets->at(3).remoteEndpoint);
+  BOOST_CHECK_NE(this->receivedPackets->at(0).remoteEndpoint,
+                 this->receivedPackets->at(2).remoteEndpoint);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestMulticastUdpTransport
diff --git a/tests/daemon/face/stream-transport.t.cpp b/tests/daemon/face/stream-transport.t.cpp
index 81044d1..33baec1 100644
--- a/tests/daemon/face/stream-transport.t.cpp
+++ b/tests/daemon/face/stream-transport.t.cpp
@@ -26,6 +26,8 @@
 #include "tcp-transport-fixture.hpp"
 #include "unix-stream-transport-fixture.hpp"
 
+#include "transport-test-common.hpp"
+
 #include <boost/mpl/vector.hpp>
 
 namespace nfd {
@@ -35,13 +37,14 @@
 BOOST_AUTO_TEST_SUITE(Face)
 BOOST_AUTO_TEST_SUITE(TestStreamTransport)
 
-typedef boost::mpl::vector<TcpTransportFixture,
-                           UnixStreamTransportFixture
-                           > StreamTransportFixtures;
+using StreamTransportFixtures = boost::mpl::vector<
+  GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(TcpTransportFixture),
+  UnixStreamTransportFixture
+>;
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(Send, T, StreamTransportFixtures, T)
 {
-  this->initialize();
+  TRANSPORT_TEST_INIT();
 
   auto block1 = ndn::encoding::makeStringBlock(300, "hello");
   this->transport->send(Transport::Packet{Block{block1}}); // make a copy of the block
@@ -69,7 +72,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveNormal, T, StreamTransportFixtures, T)
 {
-  this->initialize();
+  TRANSPORT_TEST_INIT();
 
   Block pkt = ndn::encoding::makeStringBlock(300, "hello");
   ndn::Buffer buf(pkt.begin(), pkt.end());
@@ -83,7 +86,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveMultipleSegments, T, StreamTransportFixtures, T)
 {
-  this->initialize();
+  TRANSPORT_TEST_INIT();
 
   Block pkt = ndn::encoding::makeStringBlock(300, "hello");
   ndn::Buffer buf1(pkt.begin(), pkt.end() - 2);
@@ -106,7 +109,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveMultipleBlocks, T, StreamTransportFixtures, T)
 {
-  this->initialize();
+  TRANSPORT_TEST_INIT();
 
   Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
   Block pkt2 = ndn::encoding::makeStringBlock(301, "world");
@@ -124,7 +127,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveTooLarge, T, StreamTransportFixtures, T)
 {
-  this->initialize();
+  TRANSPORT_TEST_INIT();
 
   std::vector<uint8_t> bytes(ndn::MAX_NDN_PACKET_SIZE, 0);
   Block pkt1 = ndn::encoding::makeBinaryBlock(300, bytes.data(), bytes.size() - 6);
@@ -171,7 +174,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(Close, T, StreamTransportFixtures, T)
 {
-  this->initialize();
+  TRANSPORT_TEST_INIT();
 
   this->transport->afterStateChange.connectSingleShot([] (TransportState oldState, TransportState newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::UP);
@@ -191,7 +194,7 @@
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(RemoteClose, T, StreamTransportFixtures, T)
 {
-  this->initialize();
+  TRANSPORT_TEST_INIT();
 
   this->transport->afterStateChange.connectSingleShot([this] (TransportState oldState, TransportState newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::UP);
diff --git a/tests/daemon/face/tcp-channel.t.cpp b/tests/daemon/face/tcp-channel.t.cpp
index 1239b1b..c3ebf9c 100644
--- a/tests/daemon/face/tcp-channel.t.cpp
+++ b/tests/daemon/face/tcp-channel.t.cpp
@@ -25,7 +25,7 @@
 
 #include "tcp-channel-fixture.hpp"
 
-#include "test-netif-ip.hpp"
+#include "test-ip.hpp"
 #include <boost/mpl/vector.hpp>
 
 namespace nfd {
diff --git a/tests/daemon/face/tcp-transport-fixture.hpp b/tests/daemon/face/tcp-transport-fixture.hpp
index b792e9c..face839 100644
--- a/tests/daemon/face/tcp-transport-fixture.hpp
+++ b/tests/daemon/face/tcp-transport-fixture.hpp
@@ -30,7 +30,6 @@
 #include "face/face.hpp"
 
 #include "dummy-receive-link-service.hpp"
-#include "test-netif-ip.hpp"
 #include "tests/limited-io.hpp"
 
 namespace nfd {
@@ -74,7 +73,7 @@
   }
 
   void
-  initialize(ip::address address = ip::address_v4::loopback(),
+  initialize(ip::address address,
              ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT)
   {
     tcp::endpoint remoteEp(address, 7070);
diff --git a/tests/daemon/face/tcp-transport.t.cpp b/tests/daemon/face/tcp-transport.t.cpp
index dd3de57..5ebcc76 100644
--- a/tests/daemon/face/tcp-transport.t.cpp
+++ b/tests/daemon/face/tcp-transport.t.cpp
@@ -27,128 +27,57 @@
 
 #include "tcp-transport-fixture.hpp"
 
+#include <boost/mpl/vector.hpp>
+
 namespace nfd {
 namespace face {
 namespace tests {
 
 BOOST_AUTO_TEST_SUITE(Face)
-BOOST_FIXTURE_TEST_SUITE(TestTcpTransport, TcpTransportFixture)
+BOOST_FIXTURE_TEST_SUITE(TestTcpTransport, IpTransportFixture<TcpTransportFixture>)
 
-BOOST_AUTO_TEST_CASE(StaticPropertiesLocalIpv4)
+using TcpTransportFixtures = boost::mpl::vector<
+  GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(TcpTransportFixture)
+>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticProperties, T, TcpTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
+  TRANSPORT_TEST_INIT();
 
-  checkStaticPropertiesInitialized(*transport);
+  checkStaticPropertiesInitialized(*this->transport);
 
-  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri("tcp4://127.0.0.1:" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(), FaceUri("tcp4://127.0.0.1:7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
-}
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesLocalIpv6)
-{
-  auto address = getTestIp(AddressFamily::V6, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri("tcp6://[::1]:" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(), FaceUri("tcp6://[::1]:7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
-}
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesNonLocalIpv4)
-{
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Global);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(),
-                    FaceUri("tcp4://" + address.to_string() + ":" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(),
-                    FaceUri("tcp4://" + address.to_string() + ":7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
-}
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesNonLocalIpv6)
-{
-  auto address = getTestIp(AddressFamily::V6, AddressScope::Global);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(),
-                    FaceUri("tcp6://[" + address.to_string() + "]:" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(),
-                    FaceUri("tcp6://[" + address.to_string() + "]:7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
+  BOOST_CHECK_EQUAL(this->transport->getLocalUri(), FaceUri(tcp::endpoint(this->address, this->localEp.port())));
+  BOOST_CHECK_EQUAL(this->transport->getRemoteUri(), FaceUri(tcp::endpoint(this->address, 7070)));
+  BOOST_CHECK_EQUAL(this->transport->getScope(),
+                    this->addressScope == AddressScope::Loopback ? ndn::nfd::FACE_SCOPE_LOCAL
+                                                                 : ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
+  BOOST_CHECK_EQUAL(this->transport->getMtu(), MTU_UNLIMITED);
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
+  TRANSPORT_TEST_INIT();
 
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND), true);
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERSISTENT), true);
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERMANENT), true);
 }
 
-BOOST_AUTO_TEST_CASE(PermanentReconnect)
-{
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address, ndn::nfd::FACE_PERSISTENCY_PERMANENT);
-
-  transport->afterStateChange.connectSingleShot([this] (TransportState oldState, TransportState newState) {
-    BOOST_CHECK_EQUAL(oldState, TransportState::UP);
-    BOOST_CHECK_EQUAL(newState, TransportState::DOWN);
-    limitedIo.afterOp();
-  });
-  remoteSocket.close();
-  BOOST_REQUIRE_EQUAL(limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
-
-  transport->afterStateChange.connectSingleShot([this] (TransportState oldState, TransportState newState) {
-    BOOST_CHECK_EQUAL(oldState, TransportState::DOWN);
-    BOOST_CHECK_EQUAL(newState, TransportState::UP);
-    limitedIo.afterOp();
-  });
-  BOOST_REQUIRE_EQUAL(limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
-}
-
 BOOST_AUTO_TEST_CASE(ChangePersistencyFromPermanentWhenDown)
 {
   // when persistency is changed out of permanent while transport is DOWN,
   // the transport immediately goes into FAILED state
 
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address, ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+  TRANSPORT_TEST_INIT(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
 
-  transport->afterStateChange.connectSingleShot([this] (TransportState oldState, TransportState newState) {
-    BOOST_CHECK_EQUAL(oldState, TransportState::UP);
-    BOOST_CHECK_EQUAL(newState, TransportState::DOWN);
-    limitedIo.afterOp();
-  });
+  transport->afterStateChange.connectSingleShot(
+    [this] (TransportState oldState, TransportState newState) {
+      BOOST_CHECK_EQUAL(oldState, TransportState::UP);
+      BOOST_CHECK_EQUAL(newState, TransportState::DOWN);
+      limitedIo.afterOp();
+    });
   remoteSocket.close();
   BOOST_REQUIRE_EQUAL(limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
 
@@ -163,6 +92,28 @@
   BOOST_CHECK(didStateChange);
 }
 
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(PermanentReconnect, T, TcpTransportFixtures, T)
+{
+  TRANSPORT_TEST_INIT(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+
+  this->transport->afterStateChange.connectSingleShot(
+    [this] (TransportState oldState, TransportState newState) {
+      BOOST_CHECK_EQUAL(oldState, TransportState::UP);
+      BOOST_CHECK_EQUAL(newState, TransportState::DOWN);
+      this->limitedIo.afterOp();
+    });
+  this->remoteSocket.close();
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
+
+  this->transport->afterStateChange.connectSingleShot(
+    [this] (TransportState oldState, TransportState newState) {
+      BOOST_CHECK_EQUAL(oldState, TransportState::DOWN);
+      BOOST_CHECK_EQUAL(newState, TransportState::UP);
+      this->limitedIo.afterOp();
+    });
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
+}
+
 class PermanentTcpTransportReconnectObserver : public TcpTransport
 {
 public:
@@ -204,39 +155,39 @@
   return static_cast<double>(t.count()) / 1000000.0;
 }
 
-BOOST_AUTO_TEST_CASE(PermanentReconnectWithExponentialBackoff)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(PermanentReconnectWithExponentialBackoff, T, TcpTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
+  TRANSPORT_TEST_CHECK_PRECONDITIONS();
+  // do not initialize
 
-  tcp::endpoint remoteEp(address, 7070);
-  startAccept(remoteEp);
+  tcp::endpoint remoteEp(this->address, 7070);
+  this->startAccept(remoteEp);
 
-  tcp::socket sock(g_io);
+  tcp::socket sock(this->g_io);
   sock.async_connect(remoteEp, [this] (const boost::system::error_code& error) {
     BOOST_REQUIRE_EQUAL(error, boost::system::errc::success);
-    limitedIo.afterOp();
+    this->limitedIo.afterOp();
   });
-  BOOST_REQUIRE_EQUAL(limitedIo.run(2, time::seconds(1)), LimitedIo::EXCEED_OPS);
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(2, time::seconds(1)), LimitedIo::EXCEED_OPS);
 
-  auto transportObserver = make_unique<PermanentTcpTransportReconnectObserver>(std::move(sock),
-                                                                               std::ref(limitedIo));
+  auto transportObserver =
+    make_unique<PermanentTcpTransportReconnectObserver>(std::move(sock), std::ref(this->limitedIo));
   BOOST_REQUIRE_EQUAL(transportObserver->getState(), TransportState::UP);
 
   // break the TCP connection
-  stopAccept();
-  remoteSocket.close();
+  this->stopAccept();
+  this->remoteSocket.close();
 
   // measure retry intervals
-  BOOST_REQUIRE_EQUAL(limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
   auto retryTime1 = time::steady_clock::now();
   BOOST_CHECK_EQUAL(transportObserver->getState(), TransportState::DOWN);
 
-  BOOST_REQUIRE_EQUAL(limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
   auto retryTime2 = time::steady_clock::now();
   BOOST_CHECK_EQUAL(transportObserver->getState(), TransportState::DOWN);
 
-  BOOST_REQUIRE_EQUAL(limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
   auto retryTime3 = time::steady_clock::now();
   BOOST_CHECK_EQUAL(transportObserver->getState(), TransportState::DOWN);
 
@@ -249,21 +200,21 @@
                     10.0);
 
   // reestablish the TCP connection
-  startAccept(remoteEp);
+  this->startAccept(remoteEp);
 
-  BOOST_REQUIRE_EQUAL(limitedIo.run(3, time::seconds(10)), LimitedIo::EXCEED_OPS);
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(3, time::seconds(10)), LimitedIo::EXCEED_OPS);
   BOOST_CHECK_EQUAL(transportObserver->getState(), TransportState::UP);
 
   // break the TCP connection again
-  stopAccept();
-  remoteSocket.close();
+  this->stopAccept();
+  this->remoteSocket.close();
 
   // measure retry intervals
-  BOOST_REQUIRE_EQUAL(limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
   auto retryTime4 = time::steady_clock::now();
   BOOST_CHECK_EQUAL(transportObserver->getState(), TransportState::DOWN);
 
-  BOOST_REQUIRE_EQUAL(limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
+  BOOST_REQUIRE_EQUAL(this->limitedIo.run(2, time::seconds(5)), LimitedIo::EXCEED_OPS);
   auto retryTime5 = time::steady_clock::now();
   BOOST_CHECK_EQUAL(transportObserver->getState(), TransportState::DOWN);
 
diff --git a/tests/daemon/face/tcp-udp-channel.t.cpp b/tests/daemon/face/tcp-udp-channel.t.cpp
index bac8d88..f883f22 100644
--- a/tests/daemon/face/tcp-udp-channel.t.cpp
+++ b/tests/daemon/face/tcp-udp-channel.t.cpp
@@ -26,7 +26,7 @@
 #include "tcp-channel-fixture.hpp"
 #include "udp-channel-fixture.hpp"
 
-#include "test-netif-ip.hpp"
+#include "test-ip.hpp"
 #include <boost/mpl/vector.hpp>
 
 namespace nfd {
diff --git a/tests/daemon/face/test-netif-ip.cpp b/tests/daemon/face/test-ip.cpp
similarity index 64%
copy from tests/daemon/face/test-netif-ip.cpp
copy to tests/daemon/face/test-ip.cpp
index 82f4b89..3e75d8c 100644
--- a/tests/daemon/face/test-netif-ip.cpp
+++ b/tests/daemon/face/test-ip.cpp
@@ -23,45 +23,62 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "test-netif-ip.hpp"
-#include "core/global-io.hpp"
-
-#include <ndn-cxx/net/network-monitor.hpp>
+#include "test-ip.hpp"
+#include "test-netif.hpp"
 
 namespace nfd {
+namespace face {
 namespace tests {
 
-std::vector<shared_ptr<const NetworkInterface>>
-collectNetworkInterfaces(bool allowCached)
+std::ostream&
+operator<<(std::ostream& os, AddressFamily family)
 {
-  using ndn::net::NetworkMonitor;
-
-  static std::vector<shared_ptr<const NetworkInterface>> cached;
-  // cached.empty() indicates there's no cached list of netifs.
-  // Although it could also mean a system without any network interface, this situation is rare
-  // because the loopback interface is present on almost all systems.
-
-  if (!allowCached || cached.empty()) {
-    NetworkMonitor netmon(getGlobalIoService());
-    if ((netmon.getCapabilities() & NetworkMonitor::CAP_ENUM) == 0) {
-      BOOST_THROW_EXCEPTION(NetworkMonitor::Error("NetworkMonitor::CAP_ENUM is unavailable"));
-    }
-
-    netmon.onEnumerationCompleted.connect([] { getGlobalIoService().stop(); });
-    getGlobalIoService().run();
-    getGlobalIoService().reset();
-
-    cached = netmon.listNetworkInterfaces();
+  switch (family) {
+    case AddressFamily::V4:
+      return os << "IPv4";
+    case AddressFamily::V6:
+      return os << "IPv6";
+    case AddressFamily::Any:
+      return os << "Any";
   }
+  return os << '?';
+}
 
-  return cached;
+std::ostream&
+operator<<(std::ostream& os, AddressScope scope)
+{
+  switch (scope) {
+    case AddressScope::Loopback:
+      return os << "Loopback";
+    case AddressScope::LinkLocal:
+      return os << "LinkLocal";
+    case AddressScope::Global:
+      return os << "Global";
+    case AddressScope::Any:
+      return os << "Any";
+  }
+  return os << '?';
+}
+
+std::ostream&
+operator<<(std::ostream& os, MulticastInterface mcast)
+{
+  switch (mcast) {
+    case MulticastInterface::No:
+      return os << "No";
+    case MulticastInterface::Yes:
+      return os << "Yes";
+    case MulticastInterface::Any:
+      return os << "Any";
+  }
+  return os << '?';
 }
 
 template<typename E>
 static bool
 matchTristate(E e, bool b)
 {
-  return (e == E::Unspecified) ||
+  return (e == E::Any) ||
          (e == E::Yes && b) ||
          (e == E::No && !b);
 }
@@ -76,9 +93,9 @@
     }
     for (const auto& address : interface->getNetworkAddresses()) {
       if (!address.getIp().is_unspecified() &&
-          (family == AddressFamily::UNSPECIFIED ||
-           family == address.getFamily()) &&
-          (scope == AddressScope::Unspecified ||
+          (family == AddressFamily::Any ||
+           static_cast<int>(family) == static_cast<int>(address.getFamily())) &&
+          (scope == AddressScope::Any ||
            static_cast<int>(scope) == static_cast<int>(address.getScope())) &&
           (scope != AddressScope::Loopback ||
            address.getIp().is_loopback())) {
@@ -90,4 +107,5 @@
 }
 
 } // namespace tests
+} // namespace face
 } // namespace nfd
diff --git a/tests/daemon/face/test-netif-ip.hpp b/tests/daemon/face/test-ip.hpp
similarity index 71%
rename from tests/daemon/face/test-netif-ip.hpp
rename to tests/daemon/face/test-ip.hpp
index 239bc82..1b8604e 100644
--- a/tests/daemon/face/test-netif-ip.hpp
+++ b/tests/daemon/face/test-ip.hpp
@@ -23,55 +23,46 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef NFD_TESTS_DAEMON_FACE_TEST_NETIF_IP_HPP
-#define NFD_TESTS_DAEMON_FACE_TEST_NETIF_IP_HPP
+#ifndef NFD_TESTS_DAEMON_FACE_TEST_IP_HPP
+#define NFD_TESTS_DAEMON_FACE_TEST_IP_HPP
 
 #include "core/common.hpp"
 
 #include <boost/asio/ip/address.hpp>
 #include <ndn-cxx/net/network-address.hpp>
-#include <ndn-cxx/net/network-interface.hpp>
 
 namespace nfd {
+namespace face {
 namespace tests {
 
-using ndn::net::AddressFamily;
-using ndn::net::NetworkAddress;
-using ndn::net::NetworkInterface;
+enum class AddressFamily {
+  V4 = static_cast<int>(ndn::net::AddressFamily::V4),
+  V6 = static_cast<int>(ndn::net::AddressFamily::V6),
+  Any
+};
 
-// ---- network interface ----
-
-/** \brief Collect information about network interfaces
- *  \param allowCached if true, previously collected information can be returned
- *  \note This function is blocking if \p allowCached is false or no previous information exists
- *  \throw ndn::net::NetworkMonitor::Error NetworkMonitor::CAP_ENUM is unavailable
- */
-std::vector<shared_ptr<const NetworkInterface>>
-collectNetworkInterfaces(bool allowCached = true);
-
-template<AddressFamily AF>
-bool
-hasAddressFamily(const NetworkInterface& netif)
-{
-  return std::any_of(netif.getNetworkAddresses().begin(), netif.getNetworkAddresses().end(),
-                     [] (const NetworkAddress& a) { return a.getFamily() == AF; });
-}
-
-// ---- IP address ----
+std::ostream&
+operator<<(std::ostream& os, AddressFamily family);
 
 enum class AddressScope {
   Loopback  = static_cast<int>(ndn::net::AddressScope::HOST),
   LinkLocal = static_cast<int>(ndn::net::AddressScope::LINK),
   Global    = static_cast<int>(ndn::net::AddressScope::GLOBAL),
-  Unspecified
+  Any
 };
 
+std::ostream&
+operator<<(std::ostream& os, AddressScope scope);
+
 enum class MulticastInterface {
   No,
   Yes,
-  Unspecified
+  Any
 };
 
+std::ostream&
+operator<<(std::ostream& os, MulticastInterface mcast);
+
 /** \brief Derives IP address type from AddressFamily
  */
 template<AddressFamily AF>
@@ -97,9 +88,9 @@
  *  \retval unspecified address, if no appropriate address is available
  */
 boost::asio::ip::address
-getTestIp(AddressFamily family = AddressFamily::UNSPECIFIED,
-          AddressScope scope = AddressScope::Unspecified,
-          MulticastInterface mcast = MulticastInterface::Unspecified);
+getTestIp(AddressFamily family = AddressFamily::Any,
+          AddressScope scope = AddressScope::Any,
+          MulticastInterface mcast = MulticastInterface::Any);
 
 /** \brief Skip the rest of the test case if \p address is unavailable
  *
@@ -122,6 +113,7 @@
   } while (false)
 
 } // namespace tests
+} // namespace face
 } // namespace nfd
 
-#endif // NFD_TESTS_DAEMON_FACE_TEST_NETIF_IP_HPP
+#endif // NFD_TESTS_DAEMON_FACE_TEST_IP_HPP
diff --git a/tests/daemon/face/test-netif-ip.cpp b/tests/daemon/face/test-netif.cpp
similarity index 70%
rename from tests/daemon/face/test-netif-ip.cpp
rename to tests/daemon/face/test-netif.cpp
index 82f4b89..56b9081 100644
--- a/tests/daemon/face/test-netif-ip.cpp
+++ b/tests/daemon/face/test-netif.cpp
@@ -23,12 +23,13 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "test-netif-ip.hpp"
+#include "test-netif.hpp"
 #include "core/global-io.hpp"
 
 #include <ndn-cxx/net/network-monitor.hpp>
 
 namespace nfd {
+namespace face {
 namespace tests {
 
 std::vector<shared_ptr<const NetworkInterface>>
@@ -57,37 +58,6 @@
   return cached;
 }
 
-template<typename E>
-static bool
-matchTristate(E e, bool b)
-{
-  return (e == E::Unspecified) ||
-         (e == E::Yes && b) ||
-         (e == E::No && !b);
-}
-
-boost::asio::ip::address
-getTestIp(AddressFamily family, AddressScope scope, MulticastInterface mcast)
-{
-  for (const auto& interface : collectNetworkInterfaces()) {
-    if (!interface->isUp() ||
-        !matchTristate(mcast, interface->canMulticast())) {
-      continue;
-    }
-    for (const auto& address : interface->getNetworkAddresses()) {
-      if (!address.getIp().is_unspecified() &&
-          (family == AddressFamily::UNSPECIFIED ||
-           family == address.getFamily()) &&
-          (scope == AddressScope::Unspecified ||
-           static_cast<int>(scope) == static_cast<int>(address.getScope())) &&
-          (scope != AddressScope::Loopback ||
-           address.getIp().is_loopback())) {
-        return address.getIp();
-      }
-    }
-  }
-  return {};
-}
-
 } // namespace tests
+} // namespace face
 } // namespace nfd
diff --git a/tests/daemon/face/test-netif.hpp b/tests/daemon/face/test-netif.hpp
new file mode 100644
index 0000000..d30d865
--- /dev/null
+++ b/tests/daemon/face/test-netif.hpp
@@ -0,0 +1,53 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TESTS_DAEMON_FACE_TEST_NETIF_HPP
+#define NFD_TESTS_DAEMON_FACE_TEST_NETIF_HPP
+
+#include "core/common.hpp"
+
+#include <ndn-cxx/net/network-address.hpp>
+#include <ndn-cxx/net/network-interface.hpp>
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+using ndn::net::NetworkAddress;
+using ndn::net::NetworkInterface;
+
+/** \brief Collect information about network interfaces
+ *  \param allowCached if true, previously collected information can be returned
+ *  \note This function is blocking if \p allowCached is false or no previous information exists
+ *  \throw ndn::net::NetworkMonitor::Error NetworkMonitor::CAP_ENUM is unavailable
+ */
+std::vector<shared_ptr<const NetworkInterface>>
+collectNetworkInterfaces(bool allowCached = true);
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_TESTS_DAEMON_FACE_TEST_NETIF_HPP
diff --git a/tests/daemon/face/transport-test-common.hpp b/tests/daemon/face/transport-test-common.hpp
index 16b9038..fa3e4bd 100644
--- a/tests/daemon/face/transport-test-common.hpp
+++ b/tests/daemon/face/transport-test-common.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -28,12 +28,13 @@
 
 #include "face/transport.hpp"
 #include "tests/test-common.hpp"
+#include "test-ip.hpp"
 
 namespace nfd {
 namespace face {
 namespace tests {
 
-/** \brief check a transport has all its static properties set after initialization
+/** \brief Check that a transport has all its static properties set after initialization
  *
  *  This check shall be inserted to the StaticProperties test case for each transport,
  *  in addition to checking the values of properties.
@@ -51,8 +52,66 @@
   BOOST_CHECK_NE(transport.getMtu(), MTU_INVALID);
 }
 
+/** \brief Generic wrapper for transport fixtures that require an IP address
+ */
+template<typename TransportFixtureBase,
+         AddressFamily AF = AddressFamily::Any,
+         AddressScope AS = AddressScope::Any,
+         MulticastInterface MC = MulticastInterface::Any>
+class IpTransportFixture : public TransportFixtureBase
+{
+protected:
+  IpTransportFixture()
+  {
+    BOOST_TEST_MESSAGE("Testing with AddressFamily=" << AF <<
+                       " AddressScope=" << AS <<
+                       " MulticastInterface=" << MC <<
+                       " TestIp=" << address);
+  }
+
+  std::pair<bool, std::string>
+  checkPreconditions() const
+  {
+    return {!address.is_unspecified(), "no appropriate IP address available"};
+  }
+
+  template<typename... Args>
+  void
+  initialize(Args&&... args)
+  {
+    TransportFixtureBase::initialize(address, std::forward<Args>(args)...);
+  }
+
+protected:
+  static constexpr AddressFamily addressFamily = AF;
+  static constexpr AddressScope addressScope = AS;
+  const boost::asio::ip::address address = getTestIp(AF, AS, MC);
+};
+
 } // namespace tests
 } // namespace face
 } // namespace nfd
 
+#define GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(F) \
+  IpTransportFixture<F, AddressFamily::V4, AddressScope::Loopback>,  \
+  IpTransportFixture<F, AddressFamily::V4, AddressScope::Global>,    \
+  IpTransportFixture<F, AddressFamily::V6, AddressScope::Loopback>,  \
+  IpTransportFixture<F, AddressFamily::V6, AddressScope::LinkLocal>, \
+  IpTransportFixture<F, AddressFamily::V6, AddressScope::Global>
+
+#define TRANSPORT_TEST_CHECK_PRECONDITIONS() \
+  do { \
+    auto result = this->checkPreconditions(); \
+    if (!result.first) { \
+      BOOST_WARN_MESSAGE(false, "skipping test case: " << result.second); \
+      return; \
+    } \
+  } while (false)
+
+#define TRANSPORT_TEST_INIT(...) \
+  do { \
+    TRANSPORT_TEST_CHECK_PRECONDITIONS(); \
+    this->initialize(__VA_ARGS__); \
+  } while (false)
+
 #endif // NFD_TESTS_DAEMON_FACE_TRANSPORT_TEST_COMMON_HPP
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index ce4d17e..36ccba4 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -27,7 +27,6 @@
 
 #include "face-system-fixture.hpp"
 #include "factory-test-common.hpp"
-#include "test-netif-ip.hpp"
 
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/range/algorithm/count_if.hpp>
@@ -126,7 +125,8 @@
   UdpMcastConfigFixture()
   {
     for (const auto& netif : collectNetworkInterfaces()) {
-      if (netif->isUp() && netif->canMulticast() && hasAddressFamily<AddressFamily::V4>(*netif)) {
+      if (netif->isUp() && netif->canMulticast() &&
+          hasAddressFamily(*netif, ndn::net::AddressFamily::V4)) {
         netifs.push_back(netif);
       }
     }
@@ -146,20 +146,29 @@
     return this->listUdpMcastFaces(linkType).size();
   }
 
+  /** \brief determine whether \p netif has at least one address of the given family
+   */
+  static bool
+  hasAddressFamily(const NetworkInterface& netif, ndn::net::AddressFamily af)
+  {
+    return std::any_of(netif.getNetworkAddresses().begin(), netif.getNetworkAddresses().end(),
+                       [af] (const NetworkAddress& a) { return a.getFamily() == af; });
+  }
+
   /** \brief determine whether a UDP multicast face is created on \p netif
    */
   static bool
-  isFaceOnNetif(const Face& face, const shared_ptr<const ndn::net::NetworkInterface>& netif)
+  isFaceOnNetif(const Face& face, const shared_ptr<const NetworkInterface>& netif)
   {
     auto ip = boost::asio::ip::address_v4::from_string(face.getLocalUri().getHost());
     return std::any_of(netif->getNetworkAddresses().begin(), netif->getNetworkAddresses().end(),
-                       [ip] (const ndn::net::NetworkAddress& a) { return a.getIp() == ip; });
+                       [ip] (const NetworkAddress& a) { return a.getIp() == ip; });
   }
 
 protected:
   /** \brief MulticastUdpTransport-capable network interfaces
    */
-  std::vector<shared_ptr<const ndn::net::NetworkInterface>> netifs;
+  std::vector<shared_ptr<const NetworkInterface>> netifs;
 };
 
 #define SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(n) \
diff --git a/tests/daemon/face/unicast-udp-transport-fixture.hpp b/tests/daemon/face/unicast-udp-transport-fixture.hpp
index f3c5165..e30a6a2 100644
--- a/tests/daemon/face/unicast-udp-transport-fixture.hpp
+++ b/tests/daemon/face/unicast-udp-transport-fixture.hpp
@@ -30,7 +30,6 @@
 #include "face/face.hpp"
 
 #include "dummy-receive-link-service.hpp"
-#include "test-netif-ip.hpp"
 #include "tests/limited-io.hpp"
 
 namespace nfd {
@@ -47,13 +46,12 @@
   UnicastUdpTransportFixture()
     : transport(nullptr)
     , remoteSocket(g_io)
-    , defaultAddr(getTestIp(AddressFamily::V4, AddressScope::Loopback))
     , receivedPackets(nullptr)
   {
   }
 
   void
-  initialize(ip::address address = ip::address_v4::loopback(),
+  initialize(ip::address address,
              ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT)
   {
     udp::socket sock(g_io);
@@ -111,7 +109,6 @@
   UnicastUdpTransport* transport;
   udp::endpoint localEp;
   udp::socket remoteSocket;
-  const ip::address defaultAddr;
   std::vector<Transport::Packet>* receivedPackets;
 
 private:
diff --git a/tests/daemon/face/unicast-udp-transport.t.cpp b/tests/daemon/face/unicast-udp-transport.t.cpp
index e66dc8b..075194b 100644
--- a/tests/daemon/face/unicast-udp-transport.t.cpp
+++ b/tests/daemon/face/unicast-udp-transport.t.cpp
@@ -27,86 +27,38 @@
 
 #include "unicast-udp-transport-fixture.hpp"
 
+#include <boost/mpl/vector.hpp>
+#include <boost/mpl/vector_c.hpp>
+
 namespace nfd {
 namespace face {
 namespace tests {
 
 BOOST_AUTO_TEST_SUITE(Face)
-BOOST_FIXTURE_TEST_SUITE(TestUnicastUdpTransport, UnicastUdpTransportFixture)
+BOOST_FIXTURE_TEST_SUITE(TestUnicastUdpTransport, IpTransportFixture<UnicastUdpTransportFixture>)
 
-BOOST_AUTO_TEST_CASE(StaticPropertiesLocalIpv4)
+using UnicastUdpTransportFixtures = boost::mpl::vector<
+  GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(UnicastUdpTransportFixture)
+>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticProperties, T, UnicastUdpTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
+  TRANSPORT_TEST_INIT();
 
-  checkStaticPropertiesInitialized(*transport);
+  checkStaticPropertiesInitialized(*this->transport);
 
-  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri("udp4://127.0.0.1:" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(), FaceUri("udp4://127.0.0.1:7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL); // UDP is never local
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), 65535 - 60 - 8);
-}
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesLocalIpv6)
-{
-  auto address = getTestIp(AddressFamily::V6, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri("udp6://[::1]:" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(), FaceUri("udp6://[::1]:7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL); // UDP is never local
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), 65535 - 8);
-}
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesNonLocalIpv4)
-{
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Global);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(),
-                    FaceUri("udp4://" + address.to_string() + ":" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(),
-                    FaceUri("udp4://" + address.to_string() + ":7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), 65535 - 60 - 8);
-}
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesNonLocalIpv6)
-{
-  auto address = getTestIp(AddressFamily::V6, AddressScope::Global);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(),
-                    FaceUri("udp6://[" + address.to_string() + "]:" + to_string(localEp.port())));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(),
-                    FaceUri("udp6://[" + address.to_string() + "]:7070"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), 65535 - 8);
+  BOOST_CHECK_EQUAL(this->transport->getLocalUri(), FaceUri(udp::endpoint(this->address, this->localEp.port())));
+  BOOST_CHECK_EQUAL(this->transport->getRemoteUri(), FaceUri(udp::endpoint(this->address, 7070)));
+  BOOST_CHECK_EQUAL(this->transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL); // UDP is never local
+  BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
+  BOOST_CHECK_EQUAL(this->transport->getMtu(),
+                    this->addressFamily == AddressFamily::V4 ? (65535 - 60 - 8) : (65535 - 8));
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address);
+  TRANSPORT_TEST_INIT();
 
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND), true);
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERSISTENT), true);
@@ -115,9 +67,7 @@
 
 BOOST_AUTO_TEST_CASE(ExpirationTime)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address, ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  TRANSPORT_TEST_INIT(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
   BOOST_CHECK_NE(transport->getExpirationTime(), time::steady_clock::TimePoint::max());
 
   transport->setPersistency(ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
@@ -129,9 +79,7 @@
 
 BOOST_AUTO_TEST_CASE(IdleClose)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address, ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  TRANSPORT_TEST_INIT(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
 
   int nStateChanges = 0;
   transport->afterStateChange.connect(
@@ -156,15 +104,15 @@
   BOOST_CHECK_EQUAL(nStateChanges, 2);
 }
 
-using OnDemand = std::integral_constant<ndn::nfd::FacePersistency, ndn::nfd::FACE_PERSISTENCY_ON_DEMAND>;
-using Persistent = std::integral_constant<ndn::nfd::FacePersistency, ndn::nfd::FACE_PERSISTENCY_PERSISTENT>;
-using RemoteClosePersistencies = boost::mpl::vector<OnDemand, Persistent>;
+using RemoteCloseFixture = IpTransportFixture<UnicastUdpTransportFixture,
+                                              AddressFamily::Any, AddressScope::Loopback>;
+using RemoteClosePersistencies = boost::mpl::vector_c<ndn::nfd::FacePersistency,
+                                                      ndn::nfd::FACE_PERSISTENCY_ON_DEMAND,
+                                                      ndn::nfd::FACE_PERSISTENCY_PERSISTENT>;
 
-BOOST_AUTO_TEST_CASE_TEMPLATE(RemoteClose, Persistency, RemoteClosePersistencies)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(RemoteClose, Persistency, RemoteClosePersistencies, RemoteCloseFixture)
 {
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address, Persistency::value);
+  TRANSPORT_TEST_INIT(Persistency::value);
 
   transport->afterStateChange.connectSingleShot([this] (TransportState oldState, TransportState newState) {
     BOOST_CHECK_EQUAL(oldState, TransportState::UP);
@@ -186,11 +134,9 @@
   BOOST_REQUIRE_EQUAL(limitedIo.run(1, time::seconds(1)), LimitedIo::EXCEED_OPS);
 }
 
-BOOST_AUTO_TEST_CASE(RemoteClosePermanent)
+BOOST_FIXTURE_TEST_CASE(RemoteClosePermanent, RemoteCloseFixture)
 {
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  initialize(address, ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+  TRANSPORT_TEST_INIT(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
 
   remoteSocket.close();
 
diff --git a/tests/daemon/face/unix-stream-transport-fixture.hpp b/tests/daemon/face/unix-stream-transport-fixture.hpp
index 0101f10..634bb9a 100644
--- a/tests/daemon/face/unix-stream-transport-fixture.hpp
+++ b/tests/daemon/face/unix-stream-transport-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  Regents of the University of California,
+/*
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -87,6 +87,12 @@
   {
   }
 
+  std::pair<bool, std::string>
+  checkPreconditions() const
+  {
+    return {true, ""};
+  }
+
   void
   initialize()
   {
diff --git a/tests/daemon/face/websocket-channel.t.cpp b/tests/daemon/face/websocket-channel.t.cpp
index 5c1a60d..439422f 100644
--- a/tests/daemon/face/websocket-channel.t.cpp
+++ b/tests/daemon/face/websocket-channel.t.cpp
@@ -27,7 +27,7 @@
 #include "face/websocket-transport.hpp"
 
 #include "channel-fixture.hpp"
-#include "test-netif-ip.hpp"
+#include "test-ip.hpp"
 
 namespace nfd {
 namespace face {
diff --git a/tests/daemon/face/websocket-transport-fixture.hpp b/tests/daemon/face/websocket-transport-fixture.hpp
new file mode 100644
index 0000000..6fd8e70
--- /dev/null
+++ b/tests/daemon/face/websocket-transport-fixture.hpp
@@ -0,0 +1,221 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TESTS_DAEMON_FACE_WEBSOCKET_TRANSPORT_FIXTURE_HPP
+#define NFD_TESTS_DAEMON_FACE_WEBSOCKET_TRANSPORT_FIXTURE_HPP
+
+#include "face/websocket-transport.hpp"
+#include "face/face.hpp"
+
+#include "dummy-receive-link-service.hpp"
+#include "tests/limited-io.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+using namespace nfd::tests;
+namespace ip = boost::asio::ip;
+
+/** \brief a fixture that accepts a single WebSocket connection from a client
+ */
+class WebSocketTransportFixture : public BaseFixture
+{
+protected:
+  WebSocketTransportFixture()
+    : transport(nullptr)
+    , serverReceivedPackets(nullptr)
+    , clientShouldPong(true)
+  {
+  }
+
+  /** \brief initialize server and start listening
+   */
+  void
+  serverListen(const ip::tcp::endpoint& ep,
+               const time::milliseconds& pongTimeout = time::seconds(1))
+  {
+    server.clear_access_channels(websocketpp::log::alevel::all);
+    server.clear_error_channels(websocketpp::log::elevel::all);
+
+    server.init_asio(&g_io);
+    server.set_open_handler(bind(&WebSocketTransportFixture::serverHandleOpen, this, _1));
+    server.set_close_handler(bind(&WebSocketTransportFixture::serverHandleClose, this));
+    server.set_message_handler(bind(&WebSocketTransportFixture::serverHandleMessage, this, _2));
+    server.set_pong_handler(bind(&WebSocketTransportFixture::serverHandlePong, this));
+    server.set_pong_timeout_handler(bind(&WebSocketTransportFixture::serverHandlePongTimeout, this));
+    server.set_pong_timeout(pongTimeout.count());
+
+    server.set_reuse_addr(true);
+
+    server.listen(ep);
+    server.start_accept();
+  }
+
+  /** \brief initialize client and connect to server
+   */
+  void
+  clientConnect(const std::string& uri)
+  {
+    client.clear_access_channels(websocketpp::log::alevel::all);
+    client.clear_error_channels(websocketpp::log::elevel::all);
+
+    client.init_asio(&g_io);
+    client.set_open_handler(bind(&WebSocketTransportFixture::clientHandleOpen, this, _1));
+    client.set_message_handler(bind(&WebSocketTransportFixture::clientHandleMessage, this, _2));
+    client.set_ping_handler(bind(&WebSocketTransportFixture::clientHandlePing, this));
+
+    websocketpp::lib::error_code ec;
+    auto con = client.get_connection(uri, ec);
+    BOOST_REQUIRE_EQUAL(ec, websocketpp::lib::error_code());
+
+    client.connect(con);
+  }
+
+  /** \brief initialize both server and client, and have each other connected, create Transport
+   */
+  void
+  initialize(ip::address address,
+             time::milliseconds pingInterval = time::seconds(10),
+             time::milliseconds pongTimeout = time::seconds(1))
+  {
+    ip::tcp::endpoint ep(address, 20070);
+    serverListen(ep, pongTimeout);
+    clientConnect(FaceUri(ep, "ws").toString());
+
+    BOOST_REQUIRE_EQUAL(limitedIo.run(2, // serverHandleOpen, clientHandleOpen
+                        time::seconds(1)), LimitedIo::EXCEED_OPS);
+
+    face = make_unique<Face>(
+             make_unique<DummyReceiveLinkService>(),
+             make_unique<WebSocketTransport>(serverHdl, ref(server), pingInterval));
+    transport = static_cast<WebSocketTransport*>(face->getTransport());
+    serverReceivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
+
+    BOOST_REQUIRE_EQUAL(transport->getState(), TransportState::UP);
+  }
+
+private:
+  void
+  serverHandleOpen(websocketpp::connection_hdl hdl)
+  {
+    websocketpp::lib::error_code ec;
+    auto con = server.get_con_from_hdl(hdl, ec);
+    BOOST_REQUIRE_EQUAL(ec, websocketpp::lib::error_code());
+    BOOST_REQUIRE(con);
+    remoteEp = con->get_socket().remote_endpoint();
+
+    serverHdl = hdl;
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandleClose()
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->close();
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandleMessage(websocket::Server::message_ptr msg)
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->receiveMessage(msg->get_payload());
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandlePong()
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->handlePong();
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandlePongTimeout()
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->handlePongTimeout();
+    limitedIo.afterOp();
+  }
+
+  void
+  clientHandleOpen(websocketpp::connection_hdl hdl)
+  {
+    clientHdl = hdl;
+    limitedIo.afterOp();
+  }
+
+  void
+  clientHandleMessage(websocket::Client::message_ptr msg)
+  {
+    clientReceivedMessages.push_back(msg->get_payload());
+    limitedIo.afterOp();
+  }
+
+  bool
+  clientHandlePing()
+  {
+    limitedIo.afterOp();
+    return clientShouldPong;
+  }
+
+protected:
+  LimitedIo limitedIo;
+
+  websocket::Server server;
+  websocketpp::connection_hdl serverHdl;
+  ip::tcp::endpoint remoteEp;
+  WebSocketTransport* transport;
+  std::vector<Transport::Packet>* serverReceivedPackets;
+
+  websocket::Client client;
+  websocketpp::connection_hdl clientHdl;
+  bool clientShouldPong;
+  std::vector<std::string> clientReceivedMessages;
+
+private:
+  unique_ptr<Face> face;
+};
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_TESTS_DAEMON_FACE_WEBSOCKET_TRANSPORT_FIXTURE_HPP
diff --git a/tests/daemon/face/websocket-transport.t.cpp b/tests/daemon/face/websocket-transport.t.cpp
index 6336bef..f13e065 100644
--- a/tests/daemon/face/websocket-transport.t.cpp
+++ b/tests/daemon/face/websocket-transport.t.cpp
@@ -23,380 +23,180 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "face/websocket-transport.hpp"
-#include "face/face.hpp"
-
-#include "dummy-receive-link-service.hpp"
-#include "test-netif-ip.hpp"
 #include "transport-test-common.hpp"
-#include "tests/limited-io.hpp"
+
+#include "websocket-transport-fixture.hpp"
+
+#include <boost/mpl/vector.hpp>
 
 namespace nfd {
 namespace face {
 namespace tests {
 
-using namespace nfd::tests;
-namespace ip = boost::asio::ip;
-
 BOOST_AUTO_TEST_SUITE(Face)
+BOOST_FIXTURE_TEST_SUITE(TestWebSocketTransport, IpTransportFixture<WebSocketTransportFixture>)
 
-using nfd::Face;
+using WebSocketTransportFixtures = boost::mpl::vector<
+  GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(WebSocketTransportFixture)
+>;
 
-/** \brief a fixture that accepts a single WebSocket connection from a client
- */
-class SingleWebSocketFixture : public BaseFixture
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticProperties, T, WebSocketTransportFixtures, T)
 {
-public:
-  SingleWebSocketFixture()
-    : transport(nullptr)
-    , serverReceivedPackets(nullptr)
-    , clientShouldPong(true)
-  {
-  }
+  TRANSPORT_TEST_INIT();
 
-  /** \brief initialize server and start listening
-   */
-  void
-  serverListen(const ip::tcp::endpoint& ep,
-               const time::milliseconds& pongTimeout = time::seconds(1))
-  {
-    server.clear_access_channels(websocketpp::log::alevel::all);
-    server.clear_error_channels(websocketpp::log::elevel::all);
+  checkStaticPropertiesInitialized(*this->transport);
 
-    server.init_asio(&g_io);
-    server.set_open_handler(bind(&SingleWebSocketFixture::serverHandleOpen, this, _1));
-    server.set_close_handler(bind(&SingleWebSocketFixture::serverHandleClose, this));
-    server.set_message_handler(bind(&SingleWebSocketFixture::serverHandleMessage, this, _2));
-    server.set_pong_handler(bind(&SingleWebSocketFixture::serverHandlePong, this));
-    server.set_pong_timeout_handler(bind(&SingleWebSocketFixture::serverHandlePongTimeout, this));
-    server.set_pong_timeout(pongTimeout.count());
-
-    server.set_reuse_addr(true);
-
-    server.listen(ep);
-    server.start_accept();
-  }
-
-  /** \brief initialize client and connect to server
-   */
-  void
-  clientConnect(const std::string& uri)
-  {
-    client.clear_access_channels(websocketpp::log::alevel::all);
-    client.clear_error_channels(websocketpp::log::elevel::all);
-
-    client.init_asio(&g_io);
-    client.set_open_handler(bind(&SingleWebSocketFixture::clientHandleOpen, this, _1));
-    client.set_message_handler(bind(&SingleWebSocketFixture::clientHandleMessage, this, _2));
-    client.set_ping_handler(bind(&SingleWebSocketFixture::clientHandlePing, this));
-
-    websocketpp::lib::error_code ec;
-    auto con = client.get_connection(uri, ec);
-    BOOST_REQUIRE_EQUAL(ec, websocketpp::lib::error_code());
-
-    client.connect(con);
-  }
-
-  void
-  makeFace(const time::milliseconds& pingInterval = time::seconds(10))
-  {
-    face = make_unique<Face>(
-             make_unique<DummyReceiveLinkService>(),
-             make_unique<WebSocketTransport>(serverHdl, ref(server), pingInterval));
-    transport = static_cast<WebSocketTransport*>(face->getTransport());
-    serverReceivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
-  }
-
-  /** \brief initialize both server and client, and have each other connected, create Transport
-   */
-  void
-  endToEndInitialize(const ip::tcp::endpoint& ep,
-                     const time::milliseconds& pingInterval = time::seconds(10),
-                     const time::milliseconds& pongTimeout = time::seconds(1))
-  {
-    this->serverListen(ep, pongTimeout);
-    std::string uri;
-    if (ep.address().is_v6()) {
-      uri = "ws://[" + ep.address().to_string() + "]:" + to_string(ep.port());
-    }
-    else {
-      uri = "ws://" + ep.address().to_string() + ":" + to_string(ep.port());
-    }
-    this->clientConnect(uri);
-    BOOST_REQUIRE_EQUAL(limitedIo.run(2, // serverHandleOpen, clientHandleOpen
-                        time::seconds(1)), LimitedIo::EXCEED_OPS);
-    this->makeFace(pingInterval);
-  }
-
-private:
-  void
-  serverHandleOpen(websocketpp::connection_hdl hdl)
-  {
-    websocketpp::lib::error_code ec;
-    auto con = server.get_con_from_hdl(hdl, ec);
-    BOOST_REQUIRE_EQUAL(ec, websocketpp::lib::error_code());
-    BOOST_REQUIRE(con);
-    remoteEp = con->get_socket().remote_endpoint();
-
-    serverHdl = hdl;
-    limitedIo.afterOp();
-  }
-
-  void
-  serverHandleClose()
-  {
-    if (transport == nullptr) {
-      return;
-    }
-
-    transport->close();
-    limitedIo.afterOp();
-  }
-
-  void
-  serverHandleMessage(websocket::Server::message_ptr msg)
-  {
-    if (transport == nullptr) {
-      return;
-    }
-
-    transport->receiveMessage(msg->get_payload());
-    limitedIo.afterOp();
-  }
-
-  void
-  serverHandlePong()
-  {
-    if (transport == nullptr) {
-      return;
-    }
-
-    transport->handlePong();
-    limitedIo.afterOp();
-  }
-
-  void
-  serverHandlePongTimeout()
-  {
-    if (transport == nullptr) {
-      return;
-    }
-
-    transport->handlePongTimeout();
-    limitedIo.afterOp();
-  }
-
-  void
-  clientHandleOpen(websocketpp::connection_hdl hdl)
-  {
-    clientHdl = hdl;
-    limitedIo.afterOp();
-  }
-
-  void
-  clientHandleMessage(websocket::Client::message_ptr msg)
-  {
-    clientReceivedMessages.push_back(msg->get_payload());
-    limitedIo.afterOp();
-  }
-
-  bool
-  clientHandlePing()
-  {
-    limitedIo.afterOp();
-    return clientShouldPong;
-  }
-
-public:
-  LimitedIo limitedIo;
-
-  websocket::Server server;
-  websocketpp::connection_hdl serverHdl;
-  ip::tcp::endpoint remoteEp;
-  unique_ptr<Face> face;
-  WebSocketTransport* transport;
-  std::vector<Transport::Packet>* serverReceivedPackets;
-
-  websocket::Client client;
-  websocketpp::connection_hdl clientHdl;
-  bool clientShouldPong;
-  std::vector<std::string> clientReceivedMessages;
-};
-
-BOOST_FIXTURE_TEST_SUITE(TestWebSocketTransport, SingleWebSocketFixture)
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesLocalIpv4)
-{
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  ip::tcp::endpoint ep(address, 20070);
-  this->endToEndInitialize(ep);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri(ep, "ws"));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(), FaceUri(remoteEp, "wsclient"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
+  BOOST_CHECK_EQUAL(this->transport->getLocalUri(), FaceUri(ip::tcp::endpoint(this->address, 20070), "ws"));
+  BOOST_CHECK_EQUAL(this->transport->getRemoteUri(), FaceUri(this->remoteEp, "wsclient"));
+  BOOST_CHECK_EQUAL(this->transport->getScope(),
+                    this->addressScope == AddressScope::Loopback ? ndn::nfd::FACE_SCOPE_LOCAL
+                                                                 : ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
+  BOOST_CHECK_EQUAL(this->transport->getMtu(), MTU_UNLIMITED);
 }
 
-BOOST_AUTO_TEST_CASE(StaticPropertiesNonLocalIpv4)
+using StaticPropertiesV4MappedFixtures = boost::mpl::vector<
+  IpTransportFixture<WebSocketTransportFixture, AddressFamily::V4, AddressScope::Loopback>,
+  IpTransportFixture<WebSocketTransportFixture, AddressFamily::V4, AddressScope::Global>
+>;
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticPropertiesV4Mapped, T, StaticPropertiesV4MappedFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4, AddressScope::Global);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  ip::tcp::endpoint ep(address, 20070);
-  this->endToEndInitialize(ep);
+  TRANSPORT_TEST_CHECK_PRECONDITIONS();
+  auto mappedAddr = ip::address_v6::v4_mapped(this->address.to_v4());
+  BOOST_REQUIRE(mappedAddr.is_v4_mapped());
+  WebSocketTransportFixture::initialize(mappedAddr);
 
-  checkStaticPropertiesInitialized(*transport);
+  checkStaticPropertiesInitialized(*this->transport);
 
-  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri(ep, "ws"));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(), FaceUri(remoteEp, "wsclient"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
-}
-
-BOOST_AUTO_TEST_CASE(StaticPropertiesLocalIpv4MappedIpv6)
-{
-  auto address4 = getTestIp(AddressFamily::V4, AddressScope::Loopback);
-  SKIP_IF_IP_UNAVAILABLE(address4);
-  auto address6 = ip::address_v6::v4_mapped(address4.to_v4());
-  BOOST_REQUIRE(address6.is_v4_mapped());
-  ip::tcp::endpoint ep(address6, 20070);
-  this->endToEndInitialize(ep);
-
-  checkStaticPropertiesInitialized(*transport);
-
-  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri(ep, "ws"));
-  BOOST_CHECK_EQUAL(transport->getRemoteUri(), FaceUri(remoteEp, "wsclient"));
-  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
-  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
-  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
+  BOOST_CHECK_EQUAL(this->transport->getLocalUri(), FaceUri(ip::tcp::endpoint(mappedAddr, 20070), "ws"));
+  BOOST_CHECK_EQUAL(this->transport->getRemoteUri(), FaceUri(this->remoteEp, "wsclient"));
+  BOOST_CHECK_EQUAL(this->transport->getScope(),
+                    this->addressScope == AddressScope::Loopback ? ndn::nfd::FACE_SCOPE_LOCAL
+                                                                 : ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
+  BOOST_CHECK_EQUAL(this->transport->getMtu(), MTU_UNLIMITED);
 }
 
 BOOST_AUTO_TEST_CASE(PersistencyChange)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  this->endToEndInitialize(ip::tcp::endpoint(address, 20070));
+  TRANSPORT_TEST_INIT();
 
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND), true);
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERSISTENT), false);
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERMANENT), false);
 }
 
-BOOST_AUTO_TEST_CASE(PingPong)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(PingPong, T, WebSocketTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  this->endToEndInitialize(ip::tcp::endpoint(address, 20070),
-                           time::milliseconds(500), time::milliseconds(300));
+  TRANSPORT_TEST_INIT(time::milliseconds(500), time::milliseconds(300));
 
-  BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePong
+  BOOST_CHECK_EQUAL(this->limitedIo.run(2, // clientHandlePing, serverHandlePong
                     time::milliseconds(1500)), LimitedIo::EXCEED_OPS);
 
-  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
-  BOOST_CHECK_EQUAL(transport->getCounters().nOutPings, 1);
-  BOOST_CHECK_EQUAL(transport->getCounters().nInPongs, 1);
+  BOOST_CHECK_EQUAL(this->transport->getState(), TransportState::UP);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nOutPings, 1);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInPongs, 1);
 
   this->clientShouldPong = false;
-  BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePongTimeout
+  BOOST_CHECK_EQUAL(this->limitedIo.run(2, // clientHandlePing, serverHandlePongTimeout
                     time::seconds(2)), LimitedIo::EXCEED_OPS);
 
-  BOOST_CHECK_MESSAGE(transport->getState() == TransportState::FAILED ||
-                      transport->getState() == TransportState::CLOSED,
-                      "expect FAILED or CLOSED state, actual state=" << transport->getState());
-  BOOST_CHECK_EQUAL(transport->getCounters().nOutPings, 2);
-  BOOST_CHECK_EQUAL(transport->getCounters().nInPongs, 1);
+  BOOST_CHECK_MESSAGE(this->transport->getState() == TransportState::FAILED ||
+                      this->transport->getState() == TransportState::CLOSED,
+                      "expected FAILED or CLOSED state, actual state=" << this->transport->getState());
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nOutPings, 2);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInPongs, 1);
 }
 
-BOOST_AUTO_TEST_CASE(Send)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Send, T, WebSocketTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  this->endToEndInitialize(ip::tcp::endpoint(address, 20070));
+  TRANSPORT_TEST_INIT();
+
+  auto block1 = ndn::encoding::makeStringBlock(300, "hello");
+  this->transport->send(Transport::Packet{Block{block1}}); // make a copy of the block
+  BOOST_CHECK_EQUAL(this->limitedIo.run(1, // clientHandleMessage
+                    time::seconds(1)), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nOutPackets, 1);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nOutBytes, block1.size());
+
+  auto block2 = ndn::encoding::makeStringBlock(301, "world");
+  this->transport->send(Transport::Packet{Block{block2}}); // make a copy of the block
+  BOOST_CHECK_EQUAL(this->limitedIo.run(1, // clientHandleMessage
+                    time::seconds(1)), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nOutPackets, 2);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nOutBytes, block1.size() + block2.size());
+
+  BOOST_REQUIRE_EQUAL(this->clientReceivedMessages.size(), 2);
+  BOOST_CHECK_EQUAL_COLLECTIONS(
+    reinterpret_cast<const uint8_t*>(this->clientReceivedMessages[0].data()),
+    reinterpret_cast<const uint8_t*>(this->clientReceivedMessages[0].data()) + this->clientReceivedMessages[0].size(),
+    block1.begin(), block1.end());
+  BOOST_CHECK_EQUAL_COLLECTIONS(
+    reinterpret_cast<const uint8_t*>(this->clientReceivedMessages[1].data()),
+    reinterpret_cast<const uint8_t*>(this->clientReceivedMessages[1].data()) + this->clientReceivedMessages[1].size(),
+    block2.begin(), block2.end());
+  BOOST_CHECK_EQUAL(this->transport->getState(), TransportState::UP);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveNormal, T, WebSocketTransportFixtures, T)
+{
+  TRANSPORT_TEST_INIT();
 
   Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
-  transport->send(Transport::Packet(Block(pkt1)));
-  BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
+  this->client.send(this->clientHdl, pkt1.wire(), pkt1.size(), websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(this->limitedIo.run(1, // serverHandleMessage
                     time::seconds(1)), LimitedIo::EXCEED_OPS);
 
   Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
-  transport->send(Transport::Packet(Block(pkt2)));
-  BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
+  this->client.send(this->clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(this->limitedIo.run(1, // serverHandleMessage
                     time::seconds(1)), LimitedIo::EXCEED_OPS);
 
-  BOOST_REQUIRE_EQUAL(clientReceivedMessages.size(), 2);
-  BOOST_CHECK_EQUAL_COLLECTIONS(
-    reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()),
-    reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()) + clientReceivedMessages[0].size(),
-    pkt1.begin(), pkt1.end());
-  BOOST_CHECK_EQUAL_COLLECTIONS(
-    reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()),
-    reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()) + clientReceivedMessages[1].size(),
-    pkt2.begin(), pkt2.end());
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInPackets, 2);
+  BOOST_CHECK_EQUAL(this->transport->getCounters().nInBytes, pkt1.size() + pkt2.size());
+  BOOST_CHECK_EQUAL(this->transport->getState(), TransportState::UP);
+
+  BOOST_REQUIRE_EQUAL(this->serverReceivedPackets->size(), 2);
+  BOOST_CHECK(this->serverReceivedPackets->at(0).packet == pkt1);
+  BOOST_CHECK(this->serverReceivedPackets->at(1).packet == pkt2);
+  BOOST_CHECK_EQUAL(this->serverReceivedPackets->at(0).remoteEndpoint,
+                    this->serverReceivedPackets->at(1).remoteEndpoint);
 }
 
-BOOST_AUTO_TEST_CASE(ReceiveNormal)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveMalformed, T, WebSocketTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  this->endToEndInitialize(ip::tcp::endpoint(address, 20070));
+  TRANSPORT_TEST_INIT();
 
   Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
-  client.send(clientHdl, pkt1.wire(), pkt1.size(), websocketpp::frame::opcode::binary);
-  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
-                    time::seconds(1)), LimitedIo::EXCEED_OPS);
-
-  Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
-  client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
-  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
-                    time::seconds(1)), LimitedIo::EXCEED_OPS);
-
-  BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 2);
-  BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt1);
-  BOOST_CHECK(serverReceivedPackets->at(1).packet == pkt2);
-  BOOST_CHECK_EQUAL(serverReceivedPackets->at(0).remoteEndpoint, serverReceivedPackets->at(1).remoteEndpoint);
-}
-
-BOOST_AUTO_TEST_CASE(ReceiveMalformed)
-{
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  this->endToEndInitialize(ip::tcp::endpoint(address, 20070));
-
-  Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
-  client.send(clientHdl, pkt1.wire(), pkt1.size() - 1, // truncated
-              websocketpp::frame::opcode::binary);
-  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
+  this->client.send(this->clientHdl, pkt1.wire(), pkt1.size() - 1, // truncated
+                    websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(this->limitedIo.run(1, // serverHandleMessage
                     time::seconds(1)), LimitedIo::EXCEED_OPS);
 
   // bad packet is dropped
-  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
-  BOOST_CHECK_EQUAL(serverReceivedPackets->size(), 0);
+  BOOST_CHECK_EQUAL(this->transport->getState(), TransportState::UP);
+  BOOST_CHECK_EQUAL(this->serverReceivedPackets->size(), 0);
 
   Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
-  client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
-  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
+  this->client.send(this->clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(this->limitedIo.run(1, // serverHandleMessage
                     time::seconds(1)), LimitedIo::EXCEED_OPS);
 
   // next valid packet is still received normally
-  BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 1);
-  BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt2);
+  BOOST_CHECK_EQUAL(this->transport->getState(), TransportState::UP);
+  BOOST_REQUIRE_EQUAL(this->serverReceivedPackets->size(), 1);
+  BOOST_CHECK(this->serverReceivedPackets->at(0).packet == pkt2);
 }
 
-BOOST_AUTO_TEST_CASE(Close)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(Close, T, WebSocketTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  this->endToEndInitialize(ip::tcp::endpoint(address, 20070));
+  TRANSPORT_TEST_INIT();
 
   int nStateChanges = 0;
-  transport->afterStateChange.connect(
+  this->transport->afterStateChange.connect(
     [&nStateChanges] (TransportState oldState, TransportState newState) {
       switch (nStateChanges) {
       case 0:
@@ -413,18 +213,16 @@
       nStateChanges++;
     });
 
-  transport->close();
+  this->transport->close();
   BOOST_CHECK_EQUAL(nStateChanges, 2);
 }
 
-BOOST_AUTO_TEST_CASE(RemoteClose)
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(RemoteClose, T, WebSocketTransportFixtures, T)
 {
-  auto address = getTestIp(AddressFamily::V4);
-  SKIP_IF_IP_UNAVAILABLE(address);
-  this->endToEndInitialize(ip::tcp::endpoint(address, 20070));
+  TRANSPORT_TEST_INIT();
 
   int nStateChanges = 0;
-  transport->afterStateChange.connect(
+  this->transport->afterStateChange.connect(
     [&nStateChanges] (TransportState oldState, TransportState newState) {
       switch (nStateChanges) {
       case 0:
@@ -441,8 +239,8 @@
       nStateChanges++;
     });
 
-  client.close(clientHdl, websocketpp::close::status::going_away, "");
-  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleClose
+  this->client.close(this->clientHdl, websocketpp::close::status::going_away, "");
+  BOOST_CHECK_EQUAL(this->limitedIo.run(1, // serverHandleClose
                     time::seconds(1)), LimitedIo::EXCEED_OPS);
 
   BOOST_CHECK_EQUAL(nStateChanges, 2);