Converted src/controller/tests from NL to PW unit tests. (#33351)

* Converted controller/tests from NL to PW unit tests.

* Formatting

* Omitted ControllerTests from test_components.txt
diff --git a/src/controller/tests/BUILD.gn b/src/controller/tests/BUILD.gn
index 1d93c12..1e2b15d 100644
--- a/src/controller/tests/BUILD.gn
+++ b/src/controller/tests/BUILD.gn
@@ -14,11 +14,11 @@
 
 import("//build_overrides/build.gni")
 import("//build_overrides/chip.gni")
-import("//build_overrides/nlunit_test.gni")
+import("//build_overrides/pigweed.gni")
 
 import("${chip_root}/build/chip/chip_test_suite.gni")
 
-chip_test_suite_using_nltest("tests") {
+chip_test_suite("tests") {
   output_name = "libControllerTests"
 
   test_sources = [ "TestCommissionableNodeController.cpp" ]
@@ -40,10 +40,8 @@
     "${chip_root}/src/app/tests:helpers",
     "${chip_root}/src/controller",
     "${chip_root}/src/lib/support:test_utils",
-    "${chip_root}/src/lib/support:testing_nlunit",
     "${chip_root}/src/messaging/tests:helpers",
     "${chip_root}/src/transport/raw/tests:helpers",
-    "${nlunit_test_root}:nlunit-test",
   ]
 
   if (chip_device_platform != "mbed") {
diff --git a/src/controller/tests/TestCommissionableNodeController.cpp b/src/controller/tests/TestCommissionableNodeController.cpp
index 5e866b4..b68d074 100644
--- a/src/controller/tests/TestCommissionableNodeController.cpp
+++ b/src/controller/tests/TestCommissionableNodeController.cpp
@@ -16,10 +16,10 @@
  *    limitations under the License.
  */
 
+#include <gtest/gtest.h>
+
 #include <controller/CHIPCommissionableNodeController.h>
 #include <lib/support/CHIPMemString.h>
-#include <lib/support/UnitTestRegistration.h>
-#include <nlunit-test.h>
 
 using namespace chip;
 using namespace chip::Dnssd;
@@ -61,8 +61,15 @@
 
 namespace {
 
+class TestCommissionableNodeController : public ::testing::Test
+{
+public:
+    static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
+    static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
+};
+
 #if INET_CONFIG_ENABLE_IPV4
-void TestGetDiscoveredCommissioner_HappyCase(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_HappyCase)
 {
     MockResolver resolver;
     CommissionableNodeController controller(&resolver);
@@ -76,14 +83,14 @@
 
     controller.OnNodeDiscovered(discNodeData);
 
-    NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0) != nullptr);
-    NL_TEST_ASSERT(inSuite, strcmp(inNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName) == 0);
-    NL_TEST_ASSERT(inSuite, inNodeData.ipAddress[0] == controller.GetDiscoveredCommissioner(0)->ipAddress[0]);
-    NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->port == 5540);
-    NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->numIPs == 1);
+    ASSERT_NE(controller.GetDiscoveredCommissioner(0), nullptr);
+    EXPECT_STREQ(inNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName);
+    EXPECT_EQ(inNodeData.ipAddress[0], controller.GetDiscoveredCommissioner(0)->ipAddress[0]);
+    EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->port, 5540);
+    EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->numIPs, 1u);
 }
 
-void TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr)
 {
     MockResolver resolver;
     CommissionableNodeController controller(&resolver);
@@ -98,11 +105,11 @@
 
     for (int i = 0; i < CHIP_DEVICE_CONFIG_MAX_DISCOVERED_NODES; i++)
     {
-        NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(i) == nullptr);
+        EXPECT_EQ(controller.GetDiscoveredCommissioner(i), nullptr);
     }
 }
 
-void TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode)
 {
     MockResolver resolver;
     CommissionableNodeController controller(&resolver);
@@ -123,101 +130,56 @@
     controller.OnNodeDiscovered(validDiscNodeData);
     controller.OnNodeDiscovered(invalidDiscNodeData);
 
-    NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0) != nullptr);
-    NL_TEST_ASSERT(inSuite, strcmp(validNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName) == 0);
-    NL_TEST_ASSERT(inSuite, validNodeData.ipAddress[0] == controller.GetDiscoveredCommissioner(0)->ipAddress[0]);
-    NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->port == 5540);
-    NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(0)->numIPs == 1);
+    ASSERT_NE(controller.GetDiscoveredCommissioner(0), nullptr);
+    EXPECT_STREQ(validNodeData.hostName, controller.GetDiscoveredCommissioner(0)->hostName);
+    EXPECT_EQ(validNodeData.ipAddress[0], controller.GetDiscoveredCommissioner(0)->ipAddress[0]);
+    EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->port, 5540);
+    EXPECT_EQ(controller.GetDiscoveredCommissioner(0)->numIPs, 1u);
 
-    NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(1) == nullptr);
+    EXPECT_EQ(controller.GetDiscoveredCommissioner(1), nullptr);
 }
 
 #endif // INET_CONFIG_ENABLE_IPV4
 
-void TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr)
 {
     MockResolver resolver;
     CommissionableNodeController controller(&resolver);
 
     for (int i = 0; i < CHIP_DEVICE_CONFIG_MAX_DISCOVERED_NODES; i++)
     {
-        NL_TEST_ASSERT(inSuite, controller.GetDiscoveredCommissioner(i) == nullptr);
+        EXPECT_EQ(controller.GetDiscoveredCommissioner(i), nullptr);
     }
 }
 
-void TestDiscoverCommissioners_HappyCase(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_HappyCase)
 {
     MockResolver resolver;
     CommissionableNodeController controller(&resolver);
-    NL_TEST_ASSERT(inSuite, controller.DiscoverCommissioners() == CHIP_NO_ERROR);
+    EXPECT_EQ(controller.DiscoverCommissioners(), CHIP_NO_ERROR);
 }
 
-void TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter)
 {
     MockResolver resolver;
     CommissionableNodeController controller(&resolver);
-    NL_TEST_ASSERT(inSuite,
-                   controller.DiscoverCommissioners(Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, 35)) ==
-                       CHIP_NO_ERROR);
+    EXPECT_EQ(controller.DiscoverCommissioners(Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, 35)), CHIP_NO_ERROR);
 }
 
-void TestDiscoverCommissioners_InitError_ReturnsError(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_InitError_ReturnsError)
 {
     MockResolver resolver;
     resolver.InitStatus = CHIP_ERROR_INTERNAL;
     CommissionableNodeController controller(&resolver);
-    NL_TEST_ASSERT(inSuite, controller.DiscoverCommissioners() != CHIP_NO_ERROR);
+    EXPECT_NE(controller.DiscoverCommissioners(), CHIP_NO_ERROR);
 }
 
-void TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError(nlTestSuite * inSuite, void * inContext)
+TEST_F(TestCommissionableNodeController, TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError)
 {
     MockResolver resolver;
     resolver.DiscoverCommissionersStatus = CHIP_ERROR_INTERNAL;
     CommissionableNodeController controller(&resolver);
-    NL_TEST_ASSERT(inSuite, controller.DiscoverCommissioners() != CHIP_NO_ERROR);
+    EXPECT_NE(controller.DiscoverCommissioners(), CHIP_NO_ERROR);
 }
 
-// clang-format off
-const nlTest sTests[] =
-{
-#if INET_CONFIG_ENABLE_IPV4
-    NL_TEST_DEF("TestGetDiscoveredCommissioner_HappyCase", TestGetDiscoveredCommissioner_HappyCase),
-    NL_TEST_DEF("TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode", TestGetDiscoveredCommissioner_HappyCase_OneValidOneInvalidNode),
-    NL_TEST_DEF("TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr", TestGetDiscoveredCommissioner_InvalidNodeDiscovered_ReturnsNullptr),
-#endif // INET_CONFIG_ENABLE_IPV4
-    NL_TEST_DEF("TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr", TestGetDiscoveredCommissioner_NoNodesDiscovered_ReturnsNullptr),
-    NL_TEST_DEF("TestDiscoverCommissioners_HappyCase", TestDiscoverCommissioners_HappyCase),
-    NL_TEST_DEF("TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter", TestDiscoverCommissioners_HappyCaseWithDiscoveryFilter),
-    NL_TEST_DEF("TestDiscoverCommissioners_InitError_ReturnsError", TestDiscoverCommissioners_InitError_ReturnsError),
-    NL_TEST_DEF("TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError", TestDiscoverCommissioners_DiscoverCommissionersError_ReturnsError),
-    NL_TEST_SENTINEL()
-};
-// clang-format on
-
 } // namespace
-
-int TestCommissionableNodeController_Setup(void * inContext)
-{
-    if (CHIP_NO_ERROR != chip::Platform::MemoryInit())
-    {
-        return FAILURE;
-    }
-
-    return SUCCESS;
-}
-
-int TestCommissionableNodeController_Teardown(void * inContext)
-{
-    chip::Platform::MemoryShutdown();
-    return SUCCESS;
-}
-
-int TestCommissionableNodeController()
-{
-    nlTestSuite theSuite = { "CommissionableNodeController", &sTests[0], TestCommissionableNodeController_Setup,
-                             TestCommissionableNodeController_Teardown };
-    nlTestRunner(&theSuite, nullptr);
-    return nlTestRunnerStats(&theSuite);
-}
-
-CHIP_REGISTER_TEST_SUITE(TestCommissionableNodeController)
diff --git a/src/controller/tests/TestEventCaching.cpp b/src/controller/tests/TestEventCaching.cpp
index 3ff85eb..b4c2627 100644
--- a/src/controller/tests/TestEventCaching.cpp
+++ b/src/controller/tests/TestEventCaching.cpp
@@ -16,6 +16,8 @@
  *    limitations under the License.
  */
 
+#include <gtest/gtest.h>
+
 #include "app-common/zap-generated/ids/Attributes.h"
 #include "app-common/zap-generated/ids/Clusters.h"
 #include "app/ClusterStateCache.h"
@@ -33,12 +35,9 @@
 #include <controller/InvokeInteraction.h>
 #include <lib/core/ErrorStr.h>
 #include <lib/support/TimeUtils.h>
-#include <lib/support/UnitTestContext.h>
-#include <lib/support/UnitTestRegistration.h>
 #include <lib/support/UnitTestUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <messaging/tests/MessagingContext.h>
-#include <nlunit-test.h>
 
 using namespace chip;
 using namespace chip::app;
@@ -51,40 +50,7 @@
 static uint8_t gCritEventBuffer[4096];
 static chip::app::CircularEventBuffer gCircularEventBuffer[3];
 
-class TestContext : public chip::Test::AppContext
-{
-public:
-    // Performs setup for each individual test in the test suite
-    void SetUp() override
-    {
-        const chip::app::LogStorageResources logStorageResources[] = {
-            { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug },
-            { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info },
-            { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical },
-        };
-
-        chip::Test::AppContext::SetUp();
-
-        CHIP_ERROR err = CHIP_NO_ERROR;
-        // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete
-        VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer,
-                           "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format());
-        chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources),
-                                                          gCircularEventBuffer, logStorageResources, &mEventCounter);
-    }
-
-    // Performs teardown for each individual test in the test suite
-    void TearDown() override
-    {
-        chip::app::EventManagement::DestroyEventManagement();
-        chip::Test::AppContext::TearDown();
-    }
-
-private:
-    MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
-};
-
-nlTestSuite * gSuite = nullptr;
+using TestContext = chip::Test::AppContext;
 
 //
 // The generated endpoint_config for the controller app has Endpoint 1
@@ -93,14 +59,64 @@
 //
 constexpr EndpointId kTestEndpointId = 2;
 
-class TestReadEvents
+class TestEventCaching : public ::testing::Test
 {
 public:
-    TestReadEvents() {}
-    static void TestBasicCaching(nlTestSuite * apSuite, void * apContext);
+    // Performs shared setup for all tests in the test suite
+    static void SetUpTestSuite()
+    {
+        if (mpContext == nullptr)
+        {
+            mpContext = new TestContext();
+            ASSERT_NE(mpContext, nullptr);
+        }
+        mpContext->SetUpTestSuite();
+    }
+
+    // Performs shared teardown for all tests in the test suite
+    static void TearDownTestSuite()
+    {
+        mpContext->TearDownTestSuite();
+        if (mpContext != nullptr)
+        {
+            delete mpContext;
+            mpContext = nullptr;
+        }
+    }
+
+protected:
+    // Performs setup for each test in the suite
+    void SetUp()
+    {
+        const chip::app::LogStorageResources logStorageResources[] = {
+            { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug },
+            { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info },
+            { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical },
+        };
+
+        mpContext->SetUp();
+
+        CHIP_ERROR err = CHIP_NO_ERROR;
+        // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete
+        VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer,
+                           "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format());
+        chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources),
+                                                          gCircularEventBuffer, logStorageResources, &mEventCounter);
+    }
+
+    // Performs teardown for each test in the suite
+    void TearDown()
+    {
+        chip::app::EventManagement::DestroyEventManagement();
+        mpContext->TearDown();
+    }
+
+    static TestContext * mpContext;
 
 private:
+    MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
 };
+TestContext * TestEventCaching::mpContext = nullptr;
 
 //clang-format off
 DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs)
@@ -132,7 +148,7 @@
 
 namespace {
 
-void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
+void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
 {
     CHIP_ERROR err                 = CHIP_NO_ERROR;
     static uint8_t generationCount = 0;
@@ -142,7 +158,7 @@
     for (int i = 0; i < 5; i++)
     {
         content.arg1 = static_cast<uint8_t>(generationCount++);
-        NL_TEST_ASSERT(apSuite, (err = app::LogEvent(content, kTestEndpointId, lastEventNumber)) == CHIP_NO_ERROR);
+        EXPECT_EQ((err = app::LogEvent(content, kTestEndpointId, lastEventNumber)), CHIP_NO_ERROR);
         if (i == 0)
         {
             firstEventNumber = lastEventNumber;
@@ -161,10 +177,9 @@
  * events are present in the cache.
  *
  */
-void TestReadEvents::TestBasicCaching(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestEventCaching, TestBasicCaching)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -177,8 +192,8 @@
     chip::EventNumber firstEventNumber;
     chip::EventNumber lastEventNumber;
 
-    GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
-    NL_TEST_ASSERT(apSuite, lastEventNumber > firstEventNumber);
+    GenerateEvents(firstEventNumber, lastEventNumber);
+    EXPECT_GT(lastEventNumber, firstEventNumber);
 
     app::EventPathParams eventPath;
     eventPath.mEndpointId = kTestEndpointId;
@@ -192,81 +207,81 @@
     TestReadCallback readCallback;
 
     {
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
-                                   app::ReadClient::InteractionType::Read);
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
+                                   readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         uint8_t generationCount = 0;
         readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber);
+            [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
+                EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+                EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+                EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
+                EXPECT_GE(header.mEventNumber, firstEventNumber);
+                EXPECT_LE(header.mEventNumber, lastEventNumber);
 
                 Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+                EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+                EXPECT_EQ(eventData.arg1, generationCount);
                 generationCount++;
                 return CHIP_NO_ERROR;
             });
 
-        NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1);
+        EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
 
         Optional<EventNumber> highestEventNumber;
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
+        EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
 
         //
         // Re-run the iterator but pass in a path filter: EP*/TestCluster/EID*
         //
         generationCount = 0;
         readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber);
+            [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
+                EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+                EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+                EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
+                EXPECT_GE(header.mEventNumber, firstEventNumber);
+                EXPECT_LE(header.mEventNumber, lastEventNumber);
 
                 Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+                EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+                EXPECT_EQ(eventData.arg1, generationCount);
                 generationCount++;
                 return CHIP_NO_ERROR;
             },
             app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, kInvalidEventId));
 
-        NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1);
+        EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
 
         //
         // Re-run the iterator but pass in a path filter: EP*/TestCluster/TestEvent
         //
         generationCount = 0;
         readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber);
+            [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
+                EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+                EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+                EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
+                EXPECT_GE(header.mEventNumber, firstEventNumber);
+                EXPECT_LE(header.mEventNumber, lastEventNumber);
 
                 Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+                EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+                EXPECT_EQ(eventData.arg1, generationCount);
                 generationCount++;
                 return CHIP_NO_ERROR;
             },
             app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Events::TestEvent::Id));
 
-        NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1);
+        EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
 
         //
         // Re-run the iterator but pass in a min event number filter
@@ -274,23 +289,23 @@
         //
         generationCount = 1;
         readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber + 1);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber);
+            [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
+                EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+                EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+                EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
+                EXPECT_GE(header.mEventNumber, firstEventNumber + 1);
+                EXPECT_LE(header.mEventNumber, lastEventNumber);
 
                 Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+                EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+                EXPECT_EQ(eventData.arg1, generationCount);
                 generationCount++;
                 return CHIP_NO_ERROR;
             },
             app::EventPathParams(), firstEventNumber + 1);
 
-        NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1);
+        EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
 
         //
         // Re-run the iterator but pass in a min event number filter
@@ -299,38 +314,38 @@
         //
         generationCount = 1;
         readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber >= firstEventNumber + 1);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber);
+            [&readCallback, &generationCount, firstEventNumber, lastEventNumber](const app::EventHeader & header) {
+                EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+                EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+                EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
+                EXPECT_GE(header.mEventNumber, firstEventNumber + 1);
+                EXPECT_LE(header.mEventNumber, lastEventNumber);
 
                 Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+                EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+                EXPECT_EQ(eventData.arg1, generationCount);
                 generationCount++;
                 return CHIP_NO_ERROR;
             },
             app::EventPathParams(kInvalidEndpointId, Clusters::UnitTesting::Id, kInvalidEventId), firstEventNumber + 1);
 
-        NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - firstEventNumber + 1);
+        EXPECT_EQ(generationCount, lastEventNumber - firstEventNumber + 1);
     }
 
     //
     // Generate more events.
     //
     const EventNumber oldFirstEventNumber = firstEventNumber;
-    GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
+    GenerateEvents(firstEventNumber, lastEventNumber);
 
     {
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
-                                   app::ReadClient::InteractionType::Read);
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
+                                   readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         //
         // Validate that we still have all 5 of the old events we received, as well as the new ones that just got generated.
@@ -338,27 +353,27 @@
         //
         uint8_t generationCount = 0;
         readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount, oldFirstEventNumber, lastEventNumber](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber >= oldFirstEventNumber);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber);
+            [&readCallback, &generationCount, oldFirstEventNumber, lastEventNumber](const app::EventHeader & header) {
+                EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+                EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+                EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
+                EXPECT_GE(header.mEventNumber, oldFirstEventNumber);
+                EXPECT_LE(header.mEventNumber, lastEventNumber);
 
                 Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+                EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+                EXPECT_EQ(eventData.arg1, generationCount);
                 generationCount++;
 
                 return CHIP_NO_ERROR;
             });
 
-        NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - oldFirstEventNumber + 1);
+        EXPECT_EQ(generationCount, lastEventNumber - oldFirstEventNumber + 1);
 
         Optional<EventNumber> highestEventNumber;
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
+        EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
 
         readCallback.mClusterCacheAdapter.ClearEventCache();
         generationCount = 0;
@@ -367,9 +382,9 @@
             return CHIP_NO_ERROR;
         });
 
-        NL_TEST_ASSERT(apSuite, generationCount == 0);
+        EXPECT_EQ(generationCount, 0u);
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
+        EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
     }
 
     //
@@ -377,46 +392,46 @@
     // we don't receive events lower than that value.
     //
     {
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
-                                   app::ReadClient::InteractionType::Read);
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
+                                   readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
 
         readCallback.mClusterCacheAdapter.ClearEventCache();
         constexpr EventNumber kLastSeenEventNumber = 3;
-        NL_TEST_ASSERT(apSuite, kLastSeenEventNumber < lastEventNumber);
+        EXPECT_LT(kLastSeenEventNumber, lastEventNumber);
         readCallback.mClusterCacheAdapter.SetHighestReceivedEventNumber(kLastSeenEventNumber);
         readParams.mEventNumber.ClearValue();
 
         readCallback.mEventsSeen = 0;
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         // We should only get events with event numbers larger than kHighestEventNumberSeen.
-        NL_TEST_ASSERT(apSuite, readCallback.mEventsSeen == lastEventNumber - kLastSeenEventNumber);
+        EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - kLastSeenEventNumber);
 
         uint8_t generationCount = kLastSeenEventNumber + 1;
         readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount, lastEventNumber](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber > kLastSeenEventNumber);
-                NL_TEST_ASSERT(apSuite, header.mEventNumber <= lastEventNumber);
+            [&readCallback, &generationCount, lastEventNumber, kLastSeenEventNumber](const app::EventHeader & header) {
+                EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+                EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+                EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
+                EXPECT_GT(header.mEventNumber, kLastSeenEventNumber);
+                EXPECT_LE(header.mEventNumber, lastEventNumber);
 
                 Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+                EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
+                EXPECT_EQ(eventData.arg1, generationCount);
                 generationCount++;
 
                 return CHIP_NO_ERROR;
             });
 
-        NL_TEST_ASSERT(apSuite, generationCount == lastEventNumber - oldFirstEventNumber + 1);
+        EXPECT_EQ(generationCount, lastEventNumber - oldFirstEventNumber + 1);
         Optional<EventNumber> highestEventNumber;
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
+        EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
     }
 
     //
@@ -426,65 +441,42 @@
 
     {
         readParams.mEventNumber.SetValue(5);
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
-                                   app::ReadClient::InteractionType::Read);
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
+                                   readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
         readCallback.mClusterCacheAdapter.ClearEventCache(true);
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         //
         // Validate that we would receive 5 events
         //
 
         uint8_t generationCount = 5;
-        readCallback.mClusterCacheAdapter.ForEachEventData(
-            [&apSuite, &readCallback, &generationCount](const app::EventHeader & header) {
-                NL_TEST_ASSERT(apSuite, header.mPath.mClusterId == Clusters::UnitTesting::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEventId == Clusters::UnitTesting::Events::TestEvent::Id);
-                NL_TEST_ASSERT(apSuite, header.mPath.mEndpointId == kTestEndpointId);
+        readCallback.mClusterCacheAdapter.ForEachEventData([&readCallback, &generationCount](const app::EventHeader & header) {
+            EXPECT_EQ(header.mPath.mClusterId, Clusters::UnitTesting::Id);
+            EXPECT_EQ(header.mPath.mEventId, Clusters::UnitTesting::Events::TestEvent::Id);
+            EXPECT_EQ(header.mPath.mEndpointId, kTestEndpointId);
 
-                Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
-                NL_TEST_ASSERT(apSuite, readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData) == CHIP_NO_ERROR);
+            Clusters::UnitTesting::Events::TestEvent::DecodableType eventData;
+            EXPECT_EQ(readCallback.mClusterCacheAdapter.Get(header.mEventNumber, eventData), CHIP_NO_ERROR);
 
-                NL_TEST_ASSERT(apSuite, eventData.arg1 == generationCount);
-                generationCount++;
+            EXPECT_EQ(eventData.arg1, generationCount);
+            generationCount++;
 
-                return CHIP_NO_ERROR;
-            });
+            return CHIP_NO_ERROR;
+        });
 
-        NL_TEST_ASSERT(apSuite, generationCount == 10);
+        EXPECT_EQ(generationCount, 10u);
 
         Optional<EventNumber> highestEventNumber;
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
+        EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9);
     }
 
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
 
-const nlTest sTests[] = {
-    NL_TEST_DEF("TestBasicCaching", TestReadEvents::TestBasicCaching),
-    NL_TEST_SENTINEL(),
-};
-
-nlTestSuite sSuite = {
-    "TestEventCaching",
-    &sTests[0],
-    TestContext::nlTestSetUpTestSuite,
-    TestContext::nlTestTearDownTestSuite,
-    TestContext::nlTestSetUp,
-    TestContext::nlTestTearDown,
-};
-
 } // namespace
-
-int TestEventCaching()
-{
-    gSuite = &sSuite;
-    return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
-}
-
-CHIP_REGISTER_TEST_SUITE(TestEventCaching)
diff --git a/src/controller/tests/TestEventChunking.cpp b/src/controller/tests/TestEventChunking.cpp
index fb2b068..c779095 100644
--- a/src/controller/tests/TestEventChunking.cpp
+++ b/src/controller/tests/TestEventChunking.cpp
@@ -16,6 +16,8 @@
  *    limitations under the License.
  */
 
+#include <gtest/gtest.h>
+
 #include "app-common/zap-generated/ids/Attributes.h"
 #include "app-common/zap-generated/ids/Clusters.h"
 #include "app/ConcreteAttributePath.h"
@@ -38,12 +40,8 @@
 #include <lib/core/ErrorStr.h>
 #include <lib/support/CHIPCounter.h>
 #include <lib/support/TimeUtils.h>
-#include <lib/support/UnitTestContext.h>
-#include <lib/support/UnitTestRegistration.h>
-#include <lib/support/UnitTestUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <messaging/tests/MessagingContext.h>
-#include <nlunit-test.h>
 
 using namespace chip;
 using namespace chip::app;
@@ -56,41 +54,9 @@
 static uint8_t gCritEventBuffer[4096];
 static chip::app::CircularEventBuffer gCircularEventBuffer[3];
 
-class TestContext : public chip::Test::AppContext
-{
-public:
-    // Performs setup for each individual test in the test suite
-    void SetUp() override
-    {
-        const chip::app::LogStorageResources logStorageResources[] = {
-            { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug },
-            { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info },
-            { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical },
-        };
-
-        chip::Test::AppContext::SetUp();
-
-        CHIP_ERROR err = CHIP_NO_ERROR;
-        // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete
-        VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer,
-                           "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format());
-        chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources),
-                                                          gCircularEventBuffer, logStorageResources, &mEventCounter);
-    }
-
-    // Performs teardown for each individual test in the test suite
-    void TearDown() override
-    {
-        chip::app::EventManagement::DestroyEventManagement();
-        chip::Test::AppContext::TearDown();
-    }
-
-private:
-    MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
-};
+using TestContext = chip::Test::AppContext;
 
 uint32_t gIterationCount = 0;
-nlTestSuite * gSuite     = nullptr;
 
 //
 // The generated endpoint_config for the controller app has Endpoint 1
@@ -103,16 +69,64 @@
 // The size of the attribute which is a bit larger than the size of event used in the test.
 constexpr size_t kSizeOfLargeAttribute = 60;
 
-class TestReadEvents
+class TestEventChunking : public ::testing::Test
 {
 public:
-    TestReadEvents() {}
-    static void TestEventChunking(nlTestSuite * apSuite, void * apContext);
-    static void TestMixedEventsAndAttributesChunking(nlTestSuite * apSuite, void * apContext);
-    static void TestMixedEventsAndLargeAttributesChunking(nlTestSuite * apSuite, void * apContext);
+    // Performs shared setup for all tests in the test suite
+    static void SetUpTestSuite()
+    {
+        if (mpContext == nullptr)
+        {
+            mpContext = new TestContext();
+            ASSERT_NE(mpContext, nullptr);
+        }
+        mpContext->SetUpTestSuite();
+    }
+
+    // Performs shared teardown for all tests in the test suite
+    static void TearDownTestSuite()
+    {
+        mpContext->TearDownTestSuite();
+        if (mpContext != nullptr)
+        {
+            delete mpContext;
+            mpContext = nullptr;
+        }
+    }
+
+protected:
+    // Performs setup for each test in the suite
+    void SetUp()
+    {
+        const chip::app::LogStorageResources logStorageResources[] = {
+            { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug },
+            { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info },
+            { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical },
+        };
+
+        mpContext->SetUp();
+
+        CHIP_ERROR err = CHIP_NO_ERROR;
+        // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete
+        VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer,
+                           "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format());
+        chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources),
+                                                          gCircularEventBuffer, logStorageResources, &mEventCounter);
+    }
+
+    // Performs teardown for each test in the suite
+    void TearDown()
+    {
+        chip::app::EventManagement::DestroyEventManagement();
+        mpContext->TearDown();
+    }
+
+    static TestContext * mpContext;
 
 private:
+    MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
 };
+TestContext * TestEventChunking::mpContext = nullptr;
 
 //clang-format off
 DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs)
@@ -167,30 +181,30 @@
     if (aPath.mAttributeId == Globals::Attributes::GeneratedCommandList::Id)
     {
         app::DataModel::DecodableList<CommandId> v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
         auto it          = v.begin();
         size_t arraySize = 0;
         while (it.Next())
         {
-            NL_TEST_ASSERT(gSuite, false);
+            FAIL();
         }
-        NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, arraySize == 0);
+        EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR);
+        EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR);
+        EXPECT_EQ(arraySize, 0u);
     }
     else if (aPath.mAttributeId == Globals::Attributes::AcceptedCommandList::Id)
     {
         app::DataModel::DecodableList<CommandId> v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
         auto it          = v.begin();
         size_t arraySize = 0;
         while (it.Next())
         {
-            NL_TEST_ASSERT(gSuite, false);
+            FAIL();
         }
-        NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, arraySize == 0);
+        EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR);
+        EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR);
+        EXPECT_EQ(arraySize, 0u);
     }
 #if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE
     else if (aPath.mAttributeId == Globals::Attributes::EventList::Id)
@@ -205,17 +219,17 @@
     else if (aPath.mAttributeId == kTestListLargeAttribute)
     {
         app::DataModel::DecodableList<ByteSpan> v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
         auto it          = v.begin();
         size_t arraySize = 0;
-        NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, arraySize == 4);
+        EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR);
+        EXPECT_EQ(arraySize, 4u);
     }
     else
     {
         uint8_t v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, v == (uint8_t) gIterationCount);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
+        EXPECT_EQ(v, (uint8_t) gIterationCount);
     }
     mAttributeCount++;
 }
@@ -267,9 +281,7 @@
     return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
 }
 
-namespace {
-
-void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
+void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
 {
     CHIP_ERROR err = CHIP_NO_ERROR;
 
@@ -278,7 +290,7 @@
 
     for (int i = 0; i < 5; i++)
     {
-        NL_TEST_ASSERT(apSuite, (err = app::LogEvent(content, kTestEndpointId, lastEventNumber)) == CHIP_NO_ERROR);
+        EXPECT_EQ((err = app::LogEvent(content, kTestEndpointId, lastEventNumber)), CHIP_NO_ERROR);
         if (i == 0)
         {
             firstEventNumber = lastEventNumber;
@@ -286,8 +298,6 @@
     }
 }
 
-} // namespace
-
 /*
  * This validates all the various corner cases encountered during chunking by
  * artificially reducing the size of a packet buffer used to encode attribute & event data
@@ -305,10 +315,9 @@
  * as we can possibly cover.
  *
  */
-void TestReadEvents::TestEventChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestEventChunking, TestEventChunking)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -321,7 +330,7 @@
     chip::EventNumber firstEventNumber;
     chip::EventNumber lastEventNumber;
 
-    GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
+    GenerateEvents(firstEventNumber, lastEventNumber);
 
     app::EventPathParams eventPath;
     eventPath.mEndpointId = kTestEndpointId;
@@ -349,20 +358,20 @@
 
         app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast<uint32_t>(800 + i));
 
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                    app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
-        NL_TEST_ASSERT(apSuite, readCallback.mEventCount == static_cast<uint32_t>((lastEventNumber - firstEventNumber) + 1));
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(readCallback.mEventCount, static_cast<uint32_t>((lastEventNumber - firstEventNumber) + 1));
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
         //
         // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
         //
-        if (apSuite->flagError)
+        if (HasFailure())
         {
             break;
         }
@@ -372,10 +381,9 @@
 }
 
 // Similar to the tests above, but it will read attributes AND events
-void TestReadEvents::TestMixedEventsAndAttributesChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestEventChunking, TestMixedEventsAndAttributesChunking)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -389,7 +397,7 @@
     chip::EventNumber lastEventNumber;
 
     // We will always read from the first event, so it is enough to only generate events once.
-    GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
+    GenerateEvents(firstEventNumber, lastEventNumber);
 
     app::EventPathParams eventPath;
     app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id);
@@ -418,26 +426,26 @@
 
         app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast<uint32_t>(800 + i));
 
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                    app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         //
         // Always returns the same number of attributes read (5 + revision + GlobalAttributesNotInMetadata).
         //
-        NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd);
-        NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 6 + ArraySize(GlobalAttributesNotInMetadata));
-        NL_TEST_ASSERT(apSuite, readCallback.mEventCount == static_cast<uint32_t>(lastEventNumber - firstEventNumber + 1));
+        EXPECT_TRUE(readCallback.mOnReportEnd);
+        EXPECT_EQ(readCallback.mAttributeCount, 6 + ArraySize(GlobalAttributesNotInMetadata));
+        EXPECT_EQ(readCallback.mEventCount, static_cast<uint32_t>(lastEventNumber - firstEventNumber + 1));
 
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
         //
         // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
         //
-        if (apSuite->flagError)
+        if (HasFailure())
         {
             break;
         }
@@ -449,10 +457,9 @@
 // Similar to the tests above, however, there is one another case -- the event payload is very large usually, so when it is failed
 // to encode an attribute, it is usually impossible to encode a event data, so we cannot verify the case when events and attributes
 // can be encoded in to one chunk in the tests above. This test will force it by reading only one attribtue and read many events.
-void TestReadEvents::TestMixedEventsAndLargeAttributesChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestEventChunking, TestMixedEventsAndLargeAttributesChunking)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -466,7 +473,7 @@
     chip::EventNumber lastEventNumber;
 
     // We will always read from the first event, so it is enough to only generate events once.
-    GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
+    GenerateEvents(firstEventNumber, lastEventNumber);
 
     app::EventPathParams eventPath;
     app::AttributePathParams attributePath(kTestEndpointId, app::Clusters::UnitTesting::Id, kTestListLargeAttribute);
@@ -495,23 +502,23 @@
 
         app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast<uint32_t>(800 + i));
 
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                    app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
-        NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd);
-        NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1);
-        NL_TEST_ASSERT(apSuite, readCallback.mEventCount == static_cast<uint32_t>(lastEventNumber - firstEventNumber + 1));
+        EXPECT_TRUE(readCallback.mOnReportEnd);
+        EXPECT_EQ(readCallback.mAttributeCount, 1u);
+        EXPECT_EQ(readCallback.mEventCount, static_cast<uint32_t>(lastEventNumber - firstEventNumber + 1));
 
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
         //
         // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
         //
-        if (apSuite->flagError)
+        if (HasFailure())
         {
             break;
         }
@@ -520,28 +527,4 @@
     emberAfClearDynamicEndpoint(0);
 }
 
-const nlTest sTests[] = {
-    NL_TEST_DEF("TestEventChunking", TestReadEvents::TestEventChunking),
-    NL_TEST_DEF("TestMixedEventsAndAttributesChunking", TestReadEvents::TestMixedEventsAndAttributesChunking),
-    NL_TEST_DEF("TestMixedEventsAndLargeAttributesChunking", TestReadEvents::TestMixedEventsAndLargeAttributesChunking),
-    NL_TEST_SENTINEL(),
-};
-
-nlTestSuite sSuite = {
-    "TestEventChunking",
-    &sTests[0],
-    TestContext::nlTestSetUpTestSuite,
-    TestContext::nlTestTearDownTestSuite,
-    TestContext::nlTestSetUp,
-    TestContext::nlTestTearDown,
-};
-
 } // namespace
-
-int TestEventChunkingTests()
-{
-    gSuite = &sSuite;
-    return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
-}
-
-CHIP_REGISTER_TEST_SUITE(TestEventChunkingTests)
diff --git a/src/controller/tests/TestEventNumberCaching.cpp b/src/controller/tests/TestEventNumberCaching.cpp
index 1244a77..4d77813 100644
--- a/src/controller/tests/TestEventNumberCaching.cpp
+++ b/src/controller/tests/TestEventNumberCaching.cpp
@@ -16,6 +16,8 @@
  *    limitations under the License.
  */
 
+#include <gtest/gtest.h>
+
 #include "app-common/zap-generated/ids/Clusters.h"
 #include "app/ClusterStateCache.h"
 #include <app-common/zap-generated/cluster-objects.h>
@@ -30,12 +32,8 @@
 #include <controller/InvokeInteraction.h>
 #include <lib/core/ErrorStr.h>
 #include <lib/support/TimeUtils.h>
-#include <lib/support/UnitTestContext.h>
-#include <lib/support/UnitTestRegistration.h>
-#include <lib/support/UnitTestUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <messaging/tests/MessagingContext.h>
-#include <nlunit-test.h>
 
 using namespace chip;
 using namespace chip::app;
@@ -48,40 +46,7 @@
 static uint8_t gCritEventBuffer[4096];
 static chip::app::CircularEventBuffer gCircularEventBuffer[3];
 
-class TestContext : public chip::Test::AppContext
-{
-public:
-    // Performs setup for each individual test in the test suite
-    void SetUp() override
-    {
-        const chip::app::LogStorageResources logStorageResources[] = {
-            { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug },
-            { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info },
-            { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical },
-        };
-
-        chip::Test::AppContext::SetUp();
-
-        CHIP_ERROR err = CHIP_NO_ERROR;
-        // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete
-        VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer,
-                           "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format());
-        chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources),
-                                                          gCircularEventBuffer, logStorageResources, &mEventCounter);
-    }
-
-    // Performs teardown for each individual test in the test suite
-    void TearDown() override
-    {
-        chip::app::EventManagement::DestroyEventManagement();
-        chip::Test::AppContext::TearDown();
-    }
-
-private:
-    MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
-};
-
-nlTestSuite * gSuite = nullptr;
+using TestContext = chip::Test::AppContext;
 
 //
 // The generated endpoint_config for the controller app has Endpoint 1
@@ -90,14 +55,64 @@
 //
 constexpr EndpointId kTestEndpointId = 2;
 
-class TestReadEvents
+class TestEventNumberCaching : public ::testing::Test
 {
 public:
-    TestReadEvents() {}
-    static void TestEventNumberCaching(nlTestSuite * apSuite, void * apContext);
+    // Performs shared setup for all tests in the test suite
+    static void SetUpTestSuite()
+    {
+        if (mpContext == nullptr)
+        {
+            mpContext = new TestContext();
+            ASSERT_NE(mpContext, nullptr);
+        }
+        mpContext->SetUpTestSuite();
+    }
+
+    // Performs shared teardown for all tests in the test suite
+    static void TearDownTestSuite()
+    {
+        mpContext->TearDownTestSuite();
+        if (mpContext != nullptr)
+        {
+            delete mpContext;
+            mpContext = nullptr;
+        }
+    }
+
+protected:
+    // Performs setup for each test in the suite
+    void SetUp()
+    {
+        const chip::app::LogStorageResources logStorageResources[] = {
+            { &gDebugEventBuffer[0], sizeof(gDebugEventBuffer), chip::app::PriorityLevel::Debug },
+            { &gInfoEventBuffer[0], sizeof(gInfoEventBuffer), chip::app::PriorityLevel::Info },
+            { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical },
+        };
+
+        mpContext->SetUp();
+
+        CHIP_ERROR err = CHIP_NO_ERROR;
+        // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete
+        VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer,
+                           "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format());
+        chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources),
+                                                          gCircularEventBuffer, logStorageResources, &mEventCounter);
+    }
+
+    // Performs teardown for each test in the suite
+    void TearDown()
+    {
+        chip::app::EventManagement::DestroyEventManagement();
+        mpContext->TearDown();
+    }
+
+    static TestContext * mpContext;
 
 private:
+    MonotonicallyIncreasingCounter<EventNumber> mEventCounter;
 };
+TestContext * TestEventNumberCaching::mpContext = nullptr;
 
 //clang-format off
 DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs)
@@ -127,9 +142,7 @@
     size_t mEventsSeen = 0;
 };
 
-namespace {
-
-void GenerateEvents(nlTestSuite * apSuite, chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
+void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & lastEventNumber)
 {
     CHIP_ERROR err                 = CHIP_NO_ERROR;
     static uint8_t generationCount = 0;
@@ -139,7 +152,7 @@
     for (int i = 0; i < 5; i++)
     {
         content.arg1 = static_cast<uint8_t>(generationCount++);
-        NL_TEST_ASSERT(apSuite, (err = app::LogEvent(content, kTestEndpointId, lastEventNumber)) == CHIP_NO_ERROR);
+        EXPECT_EQ((err = app::LogEvent(content, kTestEndpointId, lastEventNumber)), CHIP_NO_ERROR);
         if (i == 0)
         {
             firstEventNumber = lastEventNumber;
@@ -147,18 +160,15 @@
     }
 }
 
-} // namespace
-
 /*
  * This validates event caching by forcing a bunch of events to get generated, then reading them back
  * and upon completion of that operation, check the received version from cache, and note that cache would store
  * correpsonding attribute data since data cache is disabled.
  *
  */
-void TestReadEvents::TestEventNumberCaching(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestEventNumberCaching, TestEventNumberCaching)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -171,8 +181,8 @@
     chip::EventNumber firstEventNumber;
     chip::EventNumber lastEventNumber;
 
-    GenerateEvents(apSuite, firstEventNumber, lastEventNumber);
-    NL_TEST_ASSERT(apSuite, lastEventNumber > firstEventNumber);
+    GenerateEvents(firstEventNumber, lastEventNumber);
+    EXPECT_GT(lastEventNumber, firstEventNumber);
 
     app::EventPathParams eventPath;
     eventPath.mEndpointId = kTestEndpointId;
@@ -188,25 +198,24 @@
     {
         Optional<EventNumber> highestEventNumber;
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, !highestEventNumber.HasValue());
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
-                                   app::ReadClient::InteractionType::Read);
+        EXPECT_FALSE(highestEventNumber.HasValue());
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
+                                   readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
-        NL_TEST_ASSERT(apSuite, readCallback.mEventsSeen == lastEventNumber - firstEventNumber + 1);
+        EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - firstEventNumber + 1);
 
-        readCallback.mClusterCacheAdapter.ForEachEventData([&apSuite](const app::EventHeader & header) {
+        readCallback.mClusterCacheAdapter.ForEachEventData([](const app::EventHeader & header) {
             // We are not caching data.
-            NL_TEST_ASSERT(apSuite, false);
-
+            ADD_FAILURE(); // Can't use FAIL() because lambda has non-void return type.
             return CHIP_NO_ERROR;
         });
 
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
+        EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
     }
 
     //
@@ -214,68 +223,42 @@
     // we don't receive events except ones larger than that value.
     //
     {
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(),
-                                   app::ReadClient::InteractionType::Read);
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(),
+                                   readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read);
 
         readCallback.mClusterCacheAdapter.ClearEventCache(true);
         Optional<EventNumber> highestEventNumber;
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, !highestEventNumber.HasValue());
+        EXPECT_FALSE(highestEventNumber.HasValue());
 
         const EventNumber kHighestEventNumberSeen = lastEventNumber - 1;
-        NL_TEST_ASSERT(apSuite, kHighestEventNumberSeen < lastEventNumber);
+        EXPECT_LT(kHighestEventNumberSeen, lastEventNumber);
 
         readCallback.mClusterCacheAdapter.SetHighestReceivedEventNumber(kHighestEventNumberSeen);
 
         readCallback.mEventsSeen = 0;
 
         readParams.mEventNumber.ClearValue();
-        NL_TEST_ASSERT(apSuite, !readParams.mEventNumber.HasValue());
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_FALSE(readParams.mEventNumber.HasValue());
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         // We should only get events with event numbers larger than kHighestEventNumberSeen.
-        NL_TEST_ASSERT(apSuite, readCallback.mEventsSeen == lastEventNumber - kHighestEventNumberSeen);
+        EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - kHighestEventNumberSeen);
 
-        readCallback.mClusterCacheAdapter.ForEachEventData([&apSuite](const app::EventHeader & header) {
+        readCallback.mClusterCacheAdapter.ForEachEventData([](const app::EventHeader & header) {
             // We are not caching data.
-            NL_TEST_ASSERT(apSuite, false);
-
+            ADD_FAILURE(); // Can't use FAIL() because lambda has non-void return type.
             return CHIP_NO_ERROR;
         });
 
         readCallback.mClusterCacheAdapter.GetHighestReceivedEventNumber(highestEventNumber);
-        NL_TEST_ASSERT(apSuite, highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
+        EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == lastEventNumber);
     }
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
 
-const nlTest sTests[] = {
-    NL_TEST_DEF("TestEventNumberCaching", TestReadEvents::TestEventNumberCaching),
-    NL_TEST_SENTINEL(),
-};
-
-// clang-format off
-nlTestSuite sSuite =
-{
-    "TestEventNumberCaching",
-    &sTests[0],
-    TestContext::nlTestSetUpTestSuite,
-    TestContext::nlTestTearDownTestSuite,
-    TestContext::nlTestSetUp,
-    TestContext::nlTestTearDown,
-};
-// clang-format on
-
 } // namespace
-
-int TestEventNumberCaching()
-{
-    gSuite = &sSuite;
-    return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
-}
-
-CHIP_REGISTER_TEST_SUITE(TestEventNumberCaching)
diff --git a/src/controller/tests/TestReadChunking.cpp b/src/controller/tests/TestReadChunking.cpp
index 1095e0f..8fe5e51 100644
--- a/src/controller/tests/TestReadChunking.cpp
+++ b/src/controller/tests/TestReadChunking.cpp
@@ -16,6 +16,12 @@
  *    limitations under the License.
  */
 
+#include <functional>
+#include <map>
+#include <utility>
+
+#include <gtest/gtest.h>
+
 #include "app-common/zap-generated/ids/Attributes.h"
 #include "app-common/zap-generated/ids/Clusters.h"
 #include "app/ConcreteAttributePath.h"
@@ -34,17 +40,11 @@
 #include <app/util/attribute-storage.h>
 #include <app/util/endpoint-config-api.h>
 #include <controller/InvokeInteraction.h>
-#include <functional>
 #include <lib/core/ErrorStr.h>
 #include <lib/support/TimeUtils.h>
-#include <lib/support/UnitTestContext.h>
-#include <lib/support/UnitTestRegistration.h>
 #include <lib/support/UnitTestUtils.h>
 #include <lib/support/logging/CHIPLogging.h>
-#include <map>
 #include <messaging/tests/MessagingContext.h>
-#include <nlunit-test.h>
-#include <utility>
 
 using TestContext = chip::Test::AppContext;
 using namespace chip;
@@ -54,8 +54,6 @@
 namespace {
 
 uint32_t gIterationCount = 0;
-nlTestSuite * gSuite     = nullptr;
-TestContext * gCtx       = nullptr;
 
 //
 // The generated endpoint_config for the controller app has Endpoint 1
@@ -74,18 +72,41 @@
 
 constexpr int kListAttributeItems = 5;
 
-class TestReadChunking
+class TestReadChunking : public ::testing::Test
 {
 public:
-    TestReadChunking() {}
-    static void TestChunking(nlTestSuite * apSuite, void * apContext);
-    static void TestListChunking(nlTestSuite * apSuite, void * apContext);
-    static void TestBadChunking(nlTestSuite * apSuite, void * apContext);
-    static void TestDynamicEndpoint(nlTestSuite * apSuite, void * apContext);
-    static void TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext);
+    // Performs shared setup for all tests in the test suite
+    static void SetUpTestSuite()
+    {
+        if (mpContext == nullptr)
+        {
+            mpContext = new TestContext();
+            ASSERT_NE(mpContext, nullptr);
+        }
+        mpContext->SetUpTestSuite();
+    }
 
-private:
+    // Performs shared teardown for all tests in the test suite
+    static void TearDownTestSuite()
+    {
+        mpContext->TearDownTestSuite();
+        if (mpContext != nullptr)
+        {
+            delete mpContext;
+            mpContext = nullptr;
+        }
+    }
+
+protected:
+    // Performs setup for each test in the suite
+    void SetUp() { mpContext->SetUp(); }
+
+    // Performs teardown for each test in the suite
+    void TearDown() { mpContext->TearDown(); }
+
+    static TestContext * mpContext;
 };
+TestContext * TestReadChunking::mpContext = nullptr;
 
 //clang-format off
 DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs)
@@ -271,30 +292,30 @@
     if (aPath.mAttributeId == Globals::Attributes::GeneratedCommandList::Id)
     {
         app::DataModel::DecodableList<CommandId> v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
         auto it          = v.begin();
         size_t arraySize = 0;
         while (it.Next())
         {
-            NL_TEST_ASSERT(gSuite, false);
+            FAIL();
         }
-        NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, arraySize == 0);
+        EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR);
+        EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR);
+        EXPECT_EQ(arraySize, 0u);
     }
     else if (aPath.mAttributeId == Globals::Attributes::AcceptedCommandList::Id)
     {
         app::DataModel::DecodableList<CommandId> v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
         auto it          = v.begin();
         size_t arraySize = 0;
         while (it.Next())
         {
-            NL_TEST_ASSERT(gSuite, false);
+            FAIL();
         }
-        NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, arraySize == 0);
+        EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR);
+        EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR);
+        EXPECT_EQ(arraySize, 0u);
     }
 #if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE
     else if (aPath.mAttributeId == Globals::Attributes::EventList::Id)
@@ -309,22 +330,22 @@
     else if (aPath.mAttributeId != kTestListAttribute)
     {
         uint8_t v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, v == (uint8_t) gIterationCount);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
+        EXPECT_EQ(v, (uint8_t) gIterationCount);
     }
     else
     {
         app::DataModel::DecodableList<uint8_t> v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
         auto it          = v.begin();
         size_t arraySize = 0;
         while (it.Next())
         {
-            NL_TEST_ASSERT(gSuite, it.GetValue() == static_cast<uint8_t>(gIterationCount));
+            EXPECT_EQ(it.GetValue(), static_cast<uint8_t>(gIterationCount));
         }
-        NL_TEST_ASSERT(gSuite, it.GetStatus() == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, v.ComputeSize(&arraySize) == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(gSuite, arraySize == 5);
+        EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR);
+        EXPECT_EQ(v.ComputeSize(&arraySize), CHIP_NO_ERROR);
+        EXPECT_EQ(arraySize, 5u);
     }
     mAttributeCount++;
 }
@@ -448,13 +469,13 @@
                                               const app::StatusIB & aStatus)
 {
     VerifyOrReturn(apData != nullptr);
-    NL_TEST_ASSERT(gSuite, aPath.mClusterId == Clusters::UnitTesting::Id);
+    EXPECT_EQ(aPath.mClusterId, Clusters::UnitTesting::Id);
 
     mAttributeCount++;
     if (aPath.mAttributeId <= 5)
     {
         uint8_t v;
-        NL_TEST_ASSERT(gSuite, app::DataModel::Decode(*apData, v) == CHIP_NO_ERROR);
+        EXPECT_EQ(app::DataModel::Decode(*apData, v), CHIP_NO_ERROR);
         mValues[std::make_pair(aPath.mEndpointId, aPath.mAttributeId)] = v;
 
         auto action = mActionOn.find(std::make_pair(aPath.mEndpointId, aPath.mAttributeId));
@@ -488,10 +509,9 @@
  * as we can possibly cover.
  *
  */
-void TestReadChunking::TestChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestReadChunking, TestChunking)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -522,26 +542,26 @@
 
         app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(static_cast<uint32_t>(850 + i));
 
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                    app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
-        NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd);
+        mpContext->DrainAndServiceIO();
+        EXPECT_TRUE(readCallback.mOnReportEnd);
 
         //
         // Always returns the same number of attributes read (5 + revision + GlobalAttributesNotInMetadata).
         //
-        NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 6 + ArraySize(GlobalAttributesNotInMetadata));
+        EXPECT_EQ(readCallback.mAttributeCount, 6 + ArraySize(GlobalAttributesNotInMetadata));
         readCallback.mAttributeCount = 0;
 
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
         //
         // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
         //
-        if (apSuite->flagError)
+        if (HasFailure())
         {
             break;
         }
@@ -551,10 +571,9 @@
 }
 
 // Similar to the test above, but for the list chunking feature.
-void TestReadChunking::TestListChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestReadChunking, TestListChunking)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -598,12 +617,12 @@
         app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetWriterReserved(
             static_cast<uint32_t>(maxPacketSize - packetSize));
 
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                    app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         // Up until our packets are big enough, we might just keep getting
         // errors due to the inability to encode even a single IB in a packet.
@@ -612,14 +631,13 @@
         {
             gotFailureResponse = true;
             // Check for the right error type.
-            NL_TEST_ASSERT(apSuite,
-                           StatusIB(readCallback.mReadError).mStatus == Protocols::InteractionModel::Status::ResourceExhausted);
+            EXPECT_EQ(StatusIB(readCallback.mReadError).mStatus, Protocols::InteractionModel::Status::ResourceExhausted);
         }
         else
         {
             gotSuccessfulEncode = true;
 
-            NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd);
+            EXPECT_TRUE(readCallback.mOnReportEnd);
 
             //
             // Always returns the same number of attributes read (merged by buffered read callback). The content is checked in
@@ -628,36 +646,35 @@
             // just a replace of the first read's path and buffers it all up as a
             // single value.
             //
-            NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 1);
+            EXPECT_EQ(readCallback.mAttributeCount, 1u);
             readCallback.mAttributeCount = 0;
 
             // Check that we never saw an empty-list data IB.
-            NL_TEST_ASSERT(apSuite, !readCallback.mBufferedCallback.mDecodingFailed);
-            NL_TEST_ASSERT(apSuite, !readCallback.mBufferedCallback.mSawEmptyList);
+            EXPECT_FALSE(readCallback.mBufferedCallback.mDecodingFailed);
+            EXPECT_FALSE(readCallback.mBufferedCallback.mSawEmptyList);
         }
 
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
         //
         // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
         //
-        if (apSuite->flagError)
+        if (HasFailure())
         {
             break;
         }
     }
 
     // If this fails, our smallest packet size was not small enough.
-    NL_TEST_ASSERT(apSuite, gotFailureResponse);
+    EXPECT_TRUE(gotFailureResponse);
 
     emberAfClearDynamicEndpoint(0);
 }
 
 // Read an attribute that can never fit into the buffer. Result in an empty report, server should shutdown the transaction.
-void TestReadChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestReadChunking, TestBadChunking)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -678,27 +695,27 @@
     TestReadCallback readCallback;
 
     {
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                    app::ReadClient::InteractionType::Read);
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         // The server should return an empty list as attribute data for the first report (for list chunking), and encodes nothing
         // (then shuts down the read handler) for the second report.
         //
 
         // Nothing is actually encoded. buffered callback does not handle the message to us.
-        NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == 0);
-        NL_TEST_ASSERT(apSuite, !readCallback.mOnReportEnd);
+        EXPECT_EQ(readCallback.mAttributeCount, 0u);
+        EXPECT_FALSE(readCallback.mOnReportEnd);
 
         // The server should shutted down, while the client is still alive (pending for the attribute data.)
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
     }
 
     // Sanity check
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
@@ -706,10 +723,9 @@
 /*
  * This test contains two parts, one is to enable a new endpoint on the fly, another is to disable it and re-enable it.
  */
-void TestReadChunking::TestDynamicEndpoint(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestReadChunking, TestDynamicEndpoint)
 {
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
     // Initialize the ember side server logic
@@ -729,30 +745,28 @@
 
     {
 
-        app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+        app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                    app::ReadClient::InteractionType::Subscribe);
         // Enable the new endpoint
         emberAfSetDynamicEndpoint(0, kTestEndpointId, &testEndpoint, Span<DataVersion>(dataVersionStorage));
 
-        NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+        EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
-        NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablished);
+        EXPECT_TRUE(readCallback.mOnSubscriptionEstablished);
         readCallback.mAttributeCount = 0;
 
         emberAfSetDynamicEndpoint(0, kTestEndpointId4, &testEndpoint4, Span<DataVersion>(dataVersionStorage));
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         // Ensure we have received the report, we do not care about the initial report here.
         // GlobalAttributesNotInMetadata attributes are not included in testClusterAttrsOnEndpoint4.
-        NL_TEST_ASSERT(apSuite,
-                       readCallback.mAttributeCount ==
-                           ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata));
+        EXPECT_EQ(readCallback.mAttributeCount, ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata));
 
         // We have received all report data.
-        NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd);
+        EXPECT_TRUE(readCallback.mOnReportEnd);
 
         readCallback.mAttributeCount = 0;
         readCallback.mOnReportEnd    = false;
@@ -760,7 +774,7 @@
         // Disable the new endpoint
         emberAfEndpointEnableDisable(kTestEndpointId4, false);
 
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         // We may receive some attribute reports for descriptor cluster, but we do not care about it for now.
 
@@ -770,24 +784,22 @@
         readCallback.mOnReportEnd    = false;
 
         emberAfEndpointEnableDisable(kTestEndpointId4, true);
-        ctx.DrainAndServiceIO();
+        mpContext->DrainAndServiceIO();
 
         // Ensure we have received the report, we do not care about the initial report here.
         // GlobalAttributesNotInMetadata attributes are not included in testClusterAttrsOnEndpoint4.
-        NL_TEST_ASSERT(apSuite,
-                       readCallback.mAttributeCount ==
-                           ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata));
+        EXPECT_EQ(readCallback.mAttributeCount, ArraySize(testClusterAttrsOnEndpoint4) + ArraySize(GlobalAttributesNotInMetadata));
 
         // We have received all report data.
-        NL_TEST_ASSERT(apSuite, readCallback.mOnReportEnd);
+        EXPECT_TRUE(readCallback.mOnReportEnd);
     }
 
     chip::test_utils::SleepMillis(SecondsToMilliseconds(2));
 
     // Destroying the read client will terminate the subscription transaction.
-    ctx.DrainAndServiceIO();
+    mpContext->DrainAndServiceIO();
 
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
@@ -845,20 +857,20 @@
     std::vector<AttributesList> attributesWithSameDataVersion;
 };
 
-void DriveIOUntilSubscriptionEstablished(TestMutableReadCallback * callback)
+void DriveIOUntilSubscriptionEstablished(TestContext * pContext, TestMutableReadCallback * callback)
 {
     callback->mOnReportEnd = false;
-    gCtx->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnSubscriptionEstablished; });
-    NL_TEST_ASSERT(gSuite, callback->mOnReportEnd);
-    NL_TEST_ASSERT(gSuite, callback->mOnSubscriptionEstablished);
+    pContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnSubscriptionEstablished; });
+    EXPECT_TRUE(callback->mOnReportEnd);
+    EXPECT_TRUE(callback->mOnSubscriptionEstablished);
     callback->mActionOn.clear();
 }
 
-void DriveIOUntilEndOfReport(TestMutableReadCallback * callback)
+void DriveIOUntilEndOfReport(TestContext * pContext, TestMutableReadCallback * callback)
 {
     callback->mOnReportEnd = false;
-    gCtx->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnReportEnd; });
-    NL_TEST_ASSERT(gSuite, callback->mOnReportEnd);
+    pContext->GetIOContext().DriveIOUntil(System::Clock::Seconds16(5), [&]() { return callback->mOnReportEnd; });
+    EXPECT_TRUE(callback->mOnReportEnd);
     callback->mActionOn.clear();
 }
 
@@ -866,7 +878,7 @@
 {
     for (const auto & vals : expectedValues)
     {
-        NL_TEST_ASSERT(gSuite, callback->mValues[vals.first] == vals.second);
+        EXPECT_EQ(callback->mValues[vals.first], vals.second);
     }
 }
 
@@ -879,11 +891,11 @@
     DataVersion expectedVersion = callback->mDataVersions[attrList[0]];
     for (const auto & attr : attrList)
     {
-        NL_TEST_ASSERT(gSuite, callback->mDataVersions[attr] == expectedVersion);
+        EXPECT_EQ(callback->mDataVersions[attr], expectedVersion);
     }
 }
 
-void DoTest(TestMutableReadCallback * callback, Instruction instruction)
+void DoTest(TestContext * pContext, TestMutableReadCallback * callback, Instruction instruction)
 {
     app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetMaxAttributesPerChunk(instruction.chunksize);
 
@@ -892,7 +904,7 @@
         act();
     }
 
-    DriveIOUntilEndOfReport(callback);
+    DriveIOUntilEndOfReport(pContext, callback);
 
     CheckValues(callback, instruction.expectedValues);
 
@@ -904,16 +916,12 @@
 
 }; // namespace TestSetDirtyBetweenChunksUtil
 
-void TestReadChunking::TestSetDirtyBetweenChunks(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestReadChunking, TestSetDirtyBetweenChunks)
 {
     using namespace TestSetDirtyBetweenChunksUtil;
-    TestContext & ctx                    = *static_cast<TestContext *>(apContext);
-    auto sessionHandle                   = ctx.GetSessionBobToAlice();
+    auto sessionHandle                   = mpContext->GetSessionBobToAlice();
     app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance();
 
-    gCtx   = &ctx;
-    gSuite = apSuite;
-
     // Initialize the ember side server logic
     InitDataModelHandler();
 
@@ -945,10 +953,10 @@
 
             gIterationCount = 1;
 
-            app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+            app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                        app::ReadClient::InteractionType::Subscribe);
 
-            NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+            EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
             // CASE 1 -- Touch an attribute during priming report, then verify it is included in first report after priming report.
             {
@@ -957,11 +965,11 @@
                 // We are expected to miss attributes on kTestEndpointId during initial reports.
                 ChipLogProgress(DataManagement, "Case 1-1: Set dirty during priming report.");
                 readCallback.mActionOn[AttrOnEp5<Attr1>] = TouchAttrOp(AttrOnEp1<Attr1>);
-                DriveIOUntilSubscriptionEstablished(&readCallback);
+                DriveIOUntilSubscriptionEstablished(mpContext, &readCallback);
                 CheckValues(&readCallback, { { AttrOnEp1<Attr1>, 1 } });
 
                 ChipLogProgress(DataManagement, "Case 1-2: Check for attributes missed last report.");
-                DoTest(&readCallback, Instruction{ .chunksize = 2, .expectedValues = { { AttrOnEp1<Attr1>, 2 } } });
+                DoTest(mpContext, &readCallback, Instruction{ .chunksize = 2, .expectedValues = { { AttrOnEp1<Attr1>, 2 } } });
             }
 
             // CASE 2 -- Set dirty during chunked report, the attribute is already dirty.
@@ -969,7 +977,7 @@
                 ChipLogProgress(DataManagement, "Case 2: Set dirty during chunked report by wildcard path.");
                 readCallback.mActionOn[AttrOnEp5<Attr2>] = WriteAttrOp(AttrOnEp5<Attr3>, 3);
                 DoTest(
-                    &readCallback,
+                    mpContext, &readCallback,
                     Instruction{ .chunksize      = 2,
                                  .preworks       = { WriteAttrOp(AttrOnEp5<Attr1>, 2), WriteAttrOp(AttrOnEp5<Attr2>, 2),
                                                      WriteAttrOp(AttrOnEp5<Attr3>, 2) },
@@ -983,7 +991,7 @@
                                 "Case 3-1: Set dirty during chunked report by wildcard path -- new dirty attribute.");
                 readCallback.mActionOn[AttrOnEp5<Attr2>] = WriteAttrOp(AttrOnEp5<Attr3>, 4);
                 DoTest(
-                    &readCallback,
+                    mpContext, &readCallback,
                     Instruction{ .chunksize      = 1,
                                  .preworks       = { WriteAttrOp(AttrOnEp5<Attr1>, 4), WriteAttrOp(AttrOnEp5<Attr2>, 4) },
                                  .expectedValues = { { AttrOnEp5<Attr1>, 4 }, { AttrOnEp5<Attr2>, 4 }, { AttrOnEp5<Attr3>, 4 } },
@@ -994,7 +1002,7 @@
                 app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetMaxAttributesPerChunk(1);
                 readCallback.mActionOn[AttrOnEp5<Attr2>] = WriteAttrOp(AttrOnEp5<Attr1>, 5);
                 DoTest(
-                    &readCallback,
+                    mpContext, &readCallback,
                     Instruction{ .chunksize      = 1,
                                  .preworks       = { WriteAttrOp(AttrOnEp5<Attr2>, 5), WriteAttrOp(AttrOnEp5<Attr3>, 5) },
                                  .expectedValues = { { AttrOnEp5<Attr1>, 5 }, { AttrOnEp5<Attr2>, 5 }, { AttrOnEp5<Attr3>, 5 } },
@@ -1024,18 +1032,18 @@
         {
             TestMutableReadCallback readCallback;
 
-            app::ReadClient readClient(engine, &ctx.GetExchangeManager(), readCallback.mBufferedCallback,
+            app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mBufferedCallback,
                                        app::ReadClient::InteractionType::Subscribe);
 
-            NL_TEST_ASSERT(apSuite, readClient.SendRequest(readParams) == CHIP_NO_ERROR);
+            EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR);
 
-            DriveIOUntilSubscriptionEstablished(&readCallback);
+            DriveIOUntilSubscriptionEstablished(mpContext, &readCallback);
 
             // Note, although the two attributes comes from the same cluster, they are generated by different interested paths.
             // In this case, we won't reset the path iterator.
             ChipLogProgress(DataManagement, "Case 1-1: Test set dirty during reports generated by concrete paths.");
             readCallback.mActionOn[AttrOnEp5<Attr2>] = WriteAttrOp(AttrOnEp5<Attr3>, 4);
-            DoTest(&readCallback,
+            DoTest(mpContext, &readCallback,
                    Instruction{ .chunksize      = 1,
                                 .preworks       = { WriteAttrOp(AttrOnEp5<Attr1>, 3), WriteAttrOp(AttrOnEp5<Attr2>, 3),
                                                     WriteAttrOp(AttrOnEp5<Attr3>, 3) },
@@ -1043,46 +1051,20 @@
 
             // The attribute failed to catch last report will be picked by this report.
             ChipLogProgress(DataManagement, "Case 1-2: Check for attributes missed last report.");
-            DoTest(&readCallback, { .chunksize = 1, .expectedValues = { { AttrOnEp5<Attr3>, 4 } } });
+            DoTest(mpContext, &readCallback, { .chunksize = 1, .expectedValues = { { AttrOnEp5<Attr3>, 4 } } });
         }
     }
 
     chip::test_utils::SleepMillis(SecondsToMilliseconds(3));
 
     // Destroying the read client will terminate the subscription transaction.
-    ctx.DrainAndServiceIO();
+    mpContext->DrainAndServiceIO();
 
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(1);
     emberAfClearDynamicEndpoint(0);
     app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetMaxAttributesPerChunk(UINT32_MAX);
 }
 
-const nlTest sTests[] = {
-    NL_TEST_DEF("TestChunking", TestReadChunking::TestChunking),
-    NL_TEST_DEF("TestListChunking", TestReadChunking::TestListChunking),
-    NL_TEST_DEF("TestBadChunking", TestReadChunking::TestBadChunking),
-    NL_TEST_DEF("TestDynamicEndpoint", TestReadChunking::TestDynamicEndpoint),
-    NL_TEST_DEF("TestSetDirtyBetweenChunks", TestReadChunking::TestSetDirtyBetweenChunks),
-    NL_TEST_SENTINEL(),
-};
-
-nlTestSuite sSuite = {
-    "TestReadChunking",
-    &sTests[0],
-    TestContext::nlTestSetUpTestSuite,
-    TestContext::nlTestTearDownTestSuite,
-    TestContext::nlTestSetUp,
-    TestContext::nlTestTearDown,
-};
-
 } // namespace
-
-int TestReadChunkingTests()
-{
-    gSuite = &sSuite;
-    return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
-}
-
-CHIP_REGISTER_TEST_SUITE(TestReadChunkingTests)
diff --git a/src/controller/tests/TestServerCommandDispatch.cpp b/src/controller/tests/TestServerCommandDispatch.cpp
index a87ec5d..41fab0a 100644
--- a/src/controller/tests/TestServerCommandDispatch.cpp
+++ b/src/controller/tests/TestServerCommandDispatch.cpp
@@ -22,6 +22,8 @@
  *
  */
 
+#include <gtest/gtest.h>
+
 #include "app-common/zap-generated/ids/Attributes.h"
 #include "app-common/zap-generated/ids/Clusters.h"
 #include "protocols/interaction_model/Constants.h"
@@ -33,11 +35,8 @@
 #include <controller/InvokeInteraction.h>
 #include <controller/ReadInteraction.h>
 #include <lib/core/ErrorStr.h>
-#include <lib/support/UnitTestContext.h>
-#include <lib/support/UnitTestRegistration.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <messaging/tests/MessagingContext.h>
-#include <nlunit-test.h>
 
 using TestContext = chip::Test::AppContext;
 
@@ -131,22 +130,45 @@
 
 namespace {
 
-class TestCommandInteraction
+class TestServerCommandDispatch : public ::testing::Test
 {
 public:
-    TestCommandInteraction() {}
-    static void TestNoHandler(nlTestSuite * apSuite, void * apContext);
-    static void TestDataResponse(nlTestSuite * apSuite, void * apContext);
-    static void TestDataResponseNoCommand1(nlTestSuite * apSuite, void * apContext);
-    static void TestDataResponseNoCommand2(nlTestSuite * apSuite, void * apContext);
-    static void TestDataResponseNoCommand3(nlTestSuite * apSuite, void * apContext);
-    static void TestDataResponseHandlerOverride1(nlTestSuite * apSuite, void * apContext);
-    static void TestDataResponseHandlerOverride2(nlTestSuite * apSuite, void * apContext);
+    // Performs shared setup for all tests in the test suite
+    static void SetUpTestSuite()
+    {
+        if (mpContext == nullptr)
+        {
+            mpContext = new TestContext();
+            ASSERT_NE(mpContext, nullptr);
+        }
+        mpContext->SetUpTestSuite();
+    }
 
-private:
-    static void TestDataResponseHelper(nlTestSuite * apSuite, void * apContext, const EmberAfEndpointType * aEndpoint,
-                                       bool aExpectSuccess);
+    // Performs shared teardown for all tests in the test suite
+    static void TearDownTestSuite()
+    {
+        mpContext->TearDownTestSuite();
+        if (mpContext != nullptr)
+        {
+            delete mpContext;
+            mpContext = nullptr;
+        }
+    }
+
+protected:
+    // Performs setup for each test in the suite
+    void SetUp() { mpContext->SetUp(); }
+
+    // Performs teardown for each test in the suite
+    void TearDown() { mpContext->TearDown(); }
+
+    static TestContext * mpContext;
+
+    // Helpers
+
+    static void TestDataResponseHelper(const EmberAfEndpointType * aEndpoint, bool aExpectSuccess);
 };
+TestContext * TestServerCommandDispatch::mpContext = nullptr;
 
 // We want to send a TestSimpleArgumentRequest::Type, but get a
 // TestStructArrayArgumentResponse in return, so need to shadow the actual
@@ -156,40 +178,37 @@
     using ResponseType = Clusters::UnitTesting::Commands::TestStructArrayArgumentResponse::DecodableType;
 };
 
-void TestCommandInteraction::TestNoHandler(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestServerCommandDispatch, TestNoHandler)
 {
-    TestContext & ctx = *static_cast<TestContext *>(apContext);
     FakeRequest request;
-    auto sessionHandle = ctx.GetSessionBobToAlice();
+    auto sessionHandle = mpContext->GetSessionBobToAlice();
 
     request.arg1 = true;
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [apSuite](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus,
-                                 const auto & dataResponse) {
+    auto onSuccessCb = [](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus, const auto & dataResponse) {
         //
         // We shouldn't be arriving here, since we don't have a command handler installed.
         //
-        NL_TEST_ASSERT(apSuite, false);
+        FAIL();
     };
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onFailureCb = [apSuite](CHIP_ERROR aError) {
-        NL_TEST_ASSERT(apSuite,
-                       aError.IsIMStatus() &&
-                           app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::UnsupportedEndpoint);
+    auto onFailureCb = [](CHIP_ERROR aError) {
+        EXPECT_TRUE(aError.IsIMStatus() &&
+                    app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::UnsupportedEndpoint);
     };
 
     responseDirective = kSendDataResponse;
 
-    chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
+    chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
                                            onFailureCb);
 
-    ctx.DrainAndServiceIO();
+    mpContext->DrainAndServiceIO();
 
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 }
 
 static const int kDescriptorAttributeArraySize = 254;
@@ -239,12 +258,10 @@
 
 DECLARE_DYNAMIC_ENDPOINT(testEndpoint3, testEndpointClusters3);
 
-void TestCommandInteraction::TestDataResponseHelper(nlTestSuite * apSuite, void * apContext, const EmberAfEndpointType * aEndpoint,
-                                                    bool aExpectSuccess)
+void TestServerCommandDispatch::TestDataResponseHelper(const EmberAfEndpointType * aEndpoint, bool aExpectSuccess)
 {
-    TestContext & ctx = *static_cast<TestContext *>(apContext);
     FakeRequest request;
-    auto sessionHandle = ctx.GetSessionBobToAlice();
+    auto sessionHandle = mpContext->GetSessionBobToAlice();
 
     bool onSuccessWasCalled = false;
     bool onFailureWasCalled = false;
@@ -263,23 +280,23 @@
 
     // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's
     // not safe to do so.
-    auto onSuccessCb = [apSuite, &onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus,
-                                                      const auto & dataResponse) {
+    auto onSuccessCb = [&onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus,
+                                             const auto & dataResponse) {
         uint8_t i = 0;
         auto iter = dataResponse.arg1.begin();
         while (iter.Next())
         {
             auto & item = iter.GetValue();
 
-            NL_TEST_ASSERT(apSuite, item.a == i);
-            NL_TEST_ASSERT(apSuite, item.b == false);
-            NL_TEST_ASSERT(apSuite, item.c.a == i);
-            NL_TEST_ASSERT(apSuite, item.c.b == true);
+            EXPECT_EQ(item.a, i);
+            EXPECT_FALSE(item.b);
+            EXPECT_EQ(item.c.a, i);
+            EXPECT_TRUE(item.c.b);
             i++;
         }
 
-        NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR);
-        NL_TEST_ASSERT(apSuite, dataResponse.arg6 == true);
+        EXPECT_EQ(iter.GetStatus(), CHIP_NO_ERROR);
+        EXPECT_TRUE(dataResponse.arg6);
 
         onSuccessWasCalled = true;
     };
@@ -290,36 +307,36 @@
 
     responseDirective = kSendDataResponse;
 
-    chip::Controller::InvokeCommandRequest(&ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
+    chip::Controller::InvokeCommandRequest(&mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
                                            onFailureCb);
 
-    ctx.DrainAndServiceIO();
+    mpContext->DrainAndServiceIO();
 
-    NL_TEST_ASSERT(apSuite, onSuccessWasCalled == aExpectSuccess && onFailureWasCalled != aExpectSuccess);
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_TRUE(onSuccessWasCalled == aExpectSuccess && onFailureWasCalled != aExpectSuccess);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     onSuccessWasCalled = false;
     onFailureWasCalled = false;
 
-    auto readSuccessCb = [apSuite, &onSuccessWasCalled, aExpectSuccess](const ConcreteDataAttributePath &,
-                                                                        const DataModel::DecodableList<CommandId> & commandList) {
+    auto readSuccessCb = [&onSuccessWasCalled, aExpectSuccess](const ConcreteDataAttributePath &,
+                                                               const DataModel::DecodableList<CommandId> & commandList) {
         auto count = 0;
         auto iter  = commandList.begin();
         while (iter.Next())
         {
             // We only expect 0 or 1 command ids here.
-            NL_TEST_ASSERT(apSuite, count == 0);
-            NL_TEST_ASSERT(apSuite, iter.GetValue() == Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Id);
+            EXPECT_EQ(count, 0);
+            EXPECT_EQ(iter.GetValue(), Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Id);
             ++count;
         }
-        NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR);
+        EXPECT_EQ(iter.GetStatus(), CHIP_NO_ERROR);
         if (aExpectSuccess)
         {
-            NL_TEST_ASSERT(apSuite, count == 1);
+            EXPECT_EQ(count, 1);
         }
         else
         {
-            NL_TEST_ASSERT(apSuite, count == 0);
+            EXPECT_EQ(count, 0);
         }
         onSuccessWasCalled = true;
     };
@@ -329,91 +346,60 @@
     };
 
     chip::Controller::ReadAttribute<Clusters::UnitTesting::Attributes::AcceptedCommandList::TypeInfo>(
-        &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, readSuccessCb, readFailureCb);
+        &mpContext->GetExchangeManager(), sessionHandle, kTestEndpointId, readSuccessCb, readFailureCb);
 
-    ctx.DrainAndServiceIO();
+    mpContext->DrainAndServiceIO();
 
-    NL_TEST_ASSERT(apSuite, onSuccessWasCalled && !onFailureWasCalled);
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
 
-void TestCommandInteraction::TestDataResponse(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestServerCommandDispatch, TestDataResponse)
 {
     TestClusterCommandHandler commandHandler;
-    TestDataResponseHelper(apSuite, apContext, &testEndpoint1, true);
+    TestDataResponseHelper(&testEndpoint1, true);
 }
 
-void TestCommandInteraction::TestDataResponseNoCommand1(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestServerCommandDispatch, TestDataResponseNoCommand1)
 {
     // Check what happens if we don't claim our command id is supported, by
     // overriding the acceptedCommandList with an empty list.
     TestClusterCommandHandler commandHandler;
     commandHandler.OverrideAcceptedCommands();
     commandHandler.ClaimNoCommands();
-    TestDataResponseHelper(apSuite, apContext, &testEndpoint1, false);
+    TestDataResponseHelper(&testEndpoint1, false);
 }
 
-void TestCommandInteraction::TestDataResponseNoCommand2(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestServerCommandDispatch, TestDataResponseNoCommand2)
 {
     // Check what happens if we don't claim our command id is supported, by
     // having an acceptedCommandList that ends immediately.
     TestClusterCommandHandler commandHandler;
-    TestDataResponseHelper(apSuite, apContext, &testEndpoint2, false);
+    TestDataResponseHelper(&testEndpoint2, false);
 }
 
-void TestCommandInteraction::TestDataResponseNoCommand3(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestServerCommandDispatch, TestDataResponseNoCommand3)
 {
     // Check what happens if we don't claim our command id is supported, by
     // having an acceptedCommandList that is null.
     TestClusterCommandHandler commandHandler;
-    TestDataResponseHelper(apSuite, apContext, &testEndpoint3, false);
+    TestDataResponseHelper(&testEndpoint3, false);
 }
 
-void TestCommandInteraction::TestDataResponseHandlerOverride1(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestServerCommandDispatch, TestDataResponseHandlerOverride1)
 {
     TestClusterCommandHandler commandHandler;
     commandHandler.OverrideAcceptedCommands();
-    TestDataResponseHelper(apSuite, apContext, &testEndpoint2, true);
+    TestDataResponseHelper(&testEndpoint2, true);
 }
 
-void TestCommandInteraction::TestDataResponseHandlerOverride2(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestServerCommandDispatch, TestDataResponseHandlerOverride2)
 {
     TestClusterCommandHandler commandHandler;
     commandHandler.OverrideAcceptedCommands();
-    TestDataResponseHelper(apSuite, apContext, &testEndpoint3, true);
+    TestDataResponseHelper(&testEndpoint3, true);
 }
 
-// clang-format off
-const nlTest sTests[] =
-{
-    NL_TEST_DEF("TestNoHandler", TestCommandInteraction::TestNoHandler),
-    NL_TEST_DEF("TestDataResponse", TestCommandInteraction::TestDataResponse),
-    NL_TEST_DEF("TestDataResponseNoCommand1", TestCommandInteraction::TestDataResponseNoCommand1),
-    NL_TEST_DEF("TestDataResponseNoCommand2", TestCommandInteraction::TestDataResponseNoCommand2),
-    NL_TEST_DEF("TestDataResponseNoCommand3", TestCommandInteraction::TestDataResponseNoCommand3),
-    NL_TEST_DEF("TestDataResponseHandlerOverride1", TestCommandInteraction::TestDataResponseHandlerOverride1),
-    NL_TEST_DEF("TestDataResponseHandlerOverride2", TestCommandInteraction::TestDataResponseHandlerOverride2),
-    NL_TEST_SENTINEL()
-};
-
-// clang-format on
-
-nlTestSuite sSuite = {
-    "TestCommands",
-    &sTests[0],
-    TestContext::nlTestSetUpTestSuite,
-    TestContext::nlTestTearDownTestSuite,
-    TestContext::nlTestSetUp,
-    TestContext::nlTestTearDown,
-};
-
 } // namespace
-
-int TestCommandInteractionTest()
-{
-    return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
-}
-
-CHIP_REGISTER_TEST_SUITE(TestCommandInteractionTest)
diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp
index d8c7481..60be221 100644
--- a/src/controller/tests/TestWriteChunking.cpp
+++ b/src/controller/tests/TestWriteChunking.cpp
@@ -16,6 +16,11 @@
  *    limitations under the License.
  */
 
+#include <memory>
+#include <utility>
+
+#include <gtest/gtest.h>
+
 #include "app-common/zap-generated/ids/Attributes.h"
 #include "app-common/zap-generated/ids/Clusters.h"
 #include "app/ConcreteAttributePath.h"
@@ -32,14 +37,8 @@
 #include <app/util/attribute-storage.h>
 #include <controller/InvokeInteraction.h>
 #include <lib/core/ErrorStr.h>
-#include <lib/support/UnitTestContext.h>
-#include <lib/support/UnitTestRegistration.h>
 #include <lib/support/logging/CHIPLogging.h>
 #include <messaging/tests/MessagingContext.h>
-#include <nlunit-test.h>
-
-#include <memory>
-#include <utility>
 
 using TestContext = chip::Test::AppContext;
 using namespace chip;
@@ -49,7 +48,6 @@
 namespace {
 
 uint32_t gIterationCount = 0;
-nlTestSuite * gSuite     = nullptr;
 
 //
 // The generated endpoint_config for the controller app has Endpoint 1
@@ -64,18 +62,41 @@
 // We don't really care about the content, we just need a buffer.
 uint8_t sByteSpanData[app::kMaxSecureSduLengthBytes];
 
-class TestWriteChunking
+class TestWriteChunking : public ::testing::Test
 {
 public:
-    TestWriteChunking() {}
-    static void TestListChunking(nlTestSuite * apSuite, void * apContext);
-    static void TestBadChunking(nlTestSuite * apSuite, void * apContext);
-    static void TestConflictWrite(nlTestSuite * apSuite, void * apContext);
-    static void TestNonConflictWrite(nlTestSuite * apSuite, void * apContext);
-    static void TestTransactionalList(nlTestSuite * apSuite, void * apContext);
+    // Performs shared setup for all tests in the test suite
+    static void SetUpTestSuite()
+    {
+        if (mpContext == nullptr)
+        {
+            mpContext = new TestContext();
+            ASSERT_NE(mpContext, nullptr);
+        }
+        mpContext->SetUpTestSuite();
+    }
 
-private:
+    // Performs shared teardown for all tests in the test suite
+    static void TearDownTestSuite()
+    {
+        mpContext->TearDownTestSuite();
+        if (mpContext != nullptr)
+        {
+            delete mpContext;
+            mpContext = nullptr;
+        }
+    }
+
+protected:
+    // Performs setup for each test in the suite
+    void SetUp() { mpContext->SetUp(); }
+
+    // Performs teardown for each test in the suite
+    void TearDown() { mpContext->TearDown(); }
+
+    static TestContext * mpContext;
 };
+TestContext * TestWriteChunking::mpContext = nullptr;
 
 //clang-format off
 DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint)
@@ -188,10 +209,9 @@
  * This will cause all the various corner cases encountered of closing out the various containers within the write request and
  * thoroughly and definitely validate those edge cases.
  */
-void TestWriteChunking::TestListChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestWriteChunking, TestListChunking)
 {
-    TestContext & ctx  = *static_cast<TestContext *>(apContext);
-    auto sessionHandle = ctx.GetSessionBobToAlice();
+    auto sessionHandle = mpContext->GetSessionBobToAlice();
 
     // Initialize the ember side server logic
     InitDataModelHandler();
@@ -218,16 +238,16 @@
 
         gIterationCount = i;
 
-        app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(),
+        app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(),
                                      static_cast<uint16_t>(minReservationSize + i) /* reserved buffer size */);
 
         ByteSpan list[kTestListLength];
 
         err = writeClient.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
-        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+        EXPECT_EQ(err, CHIP_NO_ERROR);
 
         err = writeClient.SendWriteRequest(sessionHandle);
-        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+        EXPECT_EQ(err, CHIP_NO_ERROR);
 
         //
         // Service the IO + Engine till we get a ReportEnd callback on the client.
@@ -236,20 +256,19 @@
         //
         for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
         {
-            ctx.DrainAndServiceIO();
+            mpContext->DrainAndServiceIO();
         }
 
-        NL_TEST_ASSERT(apSuite,
-                       writeCallback.mSuccessCount == kTestListLength + 1 /* an extra item for the empty list at the beginning */);
-        NL_TEST_ASSERT(apSuite, writeCallback.mErrorCount == 0);
-        NL_TEST_ASSERT(apSuite, writeCallback.mOnDoneCount == 1);
+        EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */);
+        EXPECT_EQ(writeCallback.mErrorCount, 0u);
+        EXPECT_EQ(writeCallback.mOnDoneCount, 1u);
 
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
         //
         // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
         //
-        if (apSuite->flagError)
+        if (HasFailure())
         {
             break;
         }
@@ -260,10 +279,9 @@
 // We encode a pretty large write payload to test the corner cases related to message layer and secure session overheads.
 // The test should gurantee that if encode returns no error, the send should also success.
 // As the actual overhead may change, we will test over a few possible payload lengths, from 850 to MTU used in write clients.
-void TestWriteChunking::TestBadChunking(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestWriteChunking, TestBadChunking)
 {
-    TestContext & ctx  = *static_cast<TestContext *>(apContext);
-    auto sessionHandle = ctx.GetSessionBobToAlice();
+    auto sessionHandle = mpContext->GetSessionBobToAlice();
 
     bool atLeastOneRequestSent   = false;
     bool atLeastOneRequestFailed = false;
@@ -288,7 +306,7 @@
 
         gIterationCount = (uint32_t) i;
 
-        app::WriteClient writeClient(&ctx.GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing());
+        app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing());
 
         ByteSpan list[kTestListLength];
         for (auto & item : list)
@@ -308,7 +326,7 @@
 
         // If we successfully encoded the attribute, then we must be able to send the message.
         err = writeClient.SendWriteRequest(sessionHandle);
-        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+        EXPECT_EQ(err, CHIP_NO_ERROR);
 
         //
         // Service the IO + Engine till we get a ReportEnd callback on the client.
@@ -317,26 +335,25 @@
         //
         for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
         {
-            ctx.DrainAndServiceIO();
+            mpContext->DrainAndServiceIO();
         }
 
-        NL_TEST_ASSERT(apSuite,
-                       writeCallback.mSuccessCount == kTestListLength + 1 /* an extra item for the empty list at the beginning */);
-        NL_TEST_ASSERT(apSuite, writeCallback.mErrorCount == 0);
-        NL_TEST_ASSERT(apSuite, writeCallback.mOnDoneCount == 1);
+        EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */);
+        EXPECT_EQ(writeCallback.mErrorCount, 0u);
+        EXPECT_EQ(writeCallback.mOnDoneCount, 1u);
 
-        NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+        EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
         //
         // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
         //
-        if (apSuite->flagError)
+        if (HasFailure())
         {
             break;
         }
     }
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
-    NL_TEST_ASSERT(apSuite, atLeastOneRequestSent && atLeastOneRequestFailed);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
+    EXPECT_TRUE(atLeastOneRequestSent && atLeastOneRequestFailed);
     emberAfClearDynamicEndpoint(0);
 }
 
@@ -344,10 +361,9 @@
  * When chunked write is enabled, it is dangerious to handle multiple write requests at the same time. In this case, we will reject
  * the latter write requests to the same attribute.
  */
-void TestWriteChunking::TestConflictWrite(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestWriteChunking, TestConflictWrite)
 {
-    TestContext & ctx  = *static_cast<TestContext *>(apContext);
-    auto sessionHandle = ctx.GetSessionBobToAlice();
+    auto sessionHandle = mpContext->GetSessionBobToAlice();
 
     // Initialize the ember side server logic
     InitDataModelHandler();
@@ -364,11 +380,11 @@
     constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128;
 
     TestWriteCallback writeCallback1;
-    app::WriteClient writeClient1(&ctx.GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(),
+    app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(),
                                   static_cast<uint16_t>(kReserveSize));
 
     TestWriteCallback writeCallback2;
-    app::WriteClient writeClient2(&ctx.GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(),
+    app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(),
                                   static_cast<uint16_t>(kReserveSize));
 
     ByteSpan list[kTestListLength];
@@ -376,17 +392,17 @@
     CHIP_ERROR err = CHIP_NO_ERROR;
 
     err = writeClient1.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
     err = writeClient2.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
 
     err = writeClient1.SendWriteRequest(sessionHandle);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
 
     err = writeClient2.SendWriteRequest(sessionHandle);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
 
-    ctx.DrainAndServiceIO();
+    mpContext->DrainAndServiceIO();
 
     {
         const TestWriteCallback * writeCallbackRef1 = &writeCallback1;
@@ -400,19 +416,17 @@
             writeCallbackRef1 = &writeCallback2;
         }
 
-        NL_TEST_ASSERT(apSuite,
-                       writeCallbackRef1->mSuccessCount ==
-                           kTestListLength + 1 /* an extra item for the empty list at the beginning */);
-        NL_TEST_ASSERT(apSuite, writeCallbackRef1->mErrorCount == 0);
-        NL_TEST_ASSERT(apSuite, writeCallbackRef2->mSuccessCount == 0);
-        NL_TEST_ASSERT(apSuite, writeCallbackRef2->mErrorCount == kTestListLength + 1);
-        NL_TEST_ASSERT(apSuite, writeCallbackRef2->mLastErrorReason.mStatus == Protocols::InteractionModel::Status::Busy);
+        EXPECT_EQ(writeCallbackRef1->mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */);
+        EXPECT_EQ(writeCallbackRef1->mErrorCount, 0u);
+        EXPECT_EQ(writeCallbackRef2->mSuccessCount, 0u);
+        EXPECT_EQ(writeCallbackRef2->mErrorCount, kTestListLength + 1);
+        EXPECT_EQ(writeCallbackRef2->mLastErrorReason.mStatus, Protocols::InteractionModel::Status::Busy);
 
-        NL_TEST_ASSERT(apSuite, writeCallbackRef1->mOnDoneCount == 1);
-        NL_TEST_ASSERT(apSuite, writeCallbackRef2->mOnDoneCount == 1);
+        EXPECT_EQ(writeCallbackRef1->mOnDoneCount, 1u);
+        EXPECT_EQ(writeCallbackRef2->mOnDoneCount, 1u);
     }
 
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
@@ -421,10 +435,9 @@
  * When chunked write is enabled, it is dangerious to handle multiple write requests at the same time. However, we will allow such
  * change when writing to different attributes in parallel.
  */
-void TestWriteChunking::TestNonConflictWrite(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestWriteChunking, TestNonConflictWrite)
 {
-    TestContext & ctx  = *static_cast<TestContext *>(apContext);
-    auto sessionHandle = ctx.GetSessionBobToAlice();
+    auto sessionHandle = mpContext->GetSessionBobToAlice();
 
     // Initialize the ember side server logic
     InitDataModelHandler();
@@ -442,11 +455,11 @@
     constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128;
 
     TestWriteCallback writeCallback1;
-    app::WriteClient writeClient1(&ctx.GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(),
+    app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional<uint16_t>::Missing(),
                                   static_cast<uint16_t>(kReserveSize));
 
     TestWriteCallback writeCallback2;
-    app::WriteClient writeClient2(&ctx.GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(),
+    app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional<uint16_t>::Missing(),
                                   static_cast<uint16_t>(kReserveSize));
 
     ByteSpan list[kTestListLength];
@@ -454,29 +467,29 @@
     CHIP_ERROR err = CHIP_NO_ERROR;
 
     err = writeClient1.EncodeAttribute(attributePath1, app::DataModel::List<ByteSpan>(list, kTestListLength));
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
     err = writeClient2.EncodeAttribute(attributePath2, app::DataModel::List<ByteSpan>(list, kTestListLength));
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
 
     err = writeClient1.SendWriteRequest(sessionHandle);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
 
     err = writeClient2.SendWriteRequest(sessionHandle);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
 
-    ctx.DrainAndServiceIO();
+    mpContext->DrainAndServiceIO();
 
     {
-        NL_TEST_ASSERT(apSuite, writeCallback1.mErrorCount == 0);
-        NL_TEST_ASSERT(apSuite, writeCallback1.mSuccessCount == kTestListLength + 1);
-        NL_TEST_ASSERT(apSuite, writeCallback2.mErrorCount == 0);
-        NL_TEST_ASSERT(apSuite, writeCallback2.mSuccessCount == kTestListLength + 1);
+        EXPECT_EQ(writeCallback1.mErrorCount, 0u);
+        EXPECT_EQ(writeCallback1.mSuccessCount, kTestListLength + 1);
+        EXPECT_EQ(writeCallback2.mErrorCount, 0u);
+        EXPECT_EQ(writeCallback2.mSuccessCount, kTestListLength + 1);
 
-        NL_TEST_ASSERT(apSuite, writeCallback1.mOnDoneCount == 1);
-        NL_TEST_ASSERT(apSuite, writeCallback2.mOnDoneCount == 1);
+        EXPECT_EQ(writeCallback1.mOnDoneCount, 1u);
+        EXPECT_EQ(writeCallback2.mOnDoneCount, 1u);
     }
 
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
@@ -510,14 +523,14 @@
     std::vector<bool> expectedStatus;
 };
 
-void RunTest(nlTestSuite * apSuite, TestContext & ctx, Instructions instructions)
+void RunTest(TestContext * pContext, Instructions instructions)
 {
     CHIP_ERROR err     = CHIP_NO_ERROR;
-    auto sessionHandle = ctx.GetSessionBobToAlice();
+    auto sessionHandle = pContext->GetSessionBobToAlice();
 
     TestWriteCallback writeCallback;
     std::unique_ptr<WriteClient> writeClient = std::make_unique<WriteClient>(
-        &ctx.GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(),
+        &pContext->GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(),
         static_cast<uint16_t>(kMaxSecureSduLengthBytes -
                               128) /* use a smaller chunk so we only need a few attributes in the write request. */);
 
@@ -525,7 +538,7 @@
     std::vector<PathStatus> status;
 
     testServer.mOnListWriteBegin = [&](const ConcreteAttributePath & aPath) {
-        NL_TEST_ASSERT(apSuite, onGoingPath == ConcreteAttributePath());
+        EXPECT_EQ(onGoingPath, ConcreteAttributePath());
         onGoingPath = aPath;
         ChipLogProgress(Zcl, "OnListWriteBegin endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI,
                         aPath.mEndpointId, ChipLogValueMEI(aPath.mClusterId), ChipLogValueMEI(aPath.mAttributeId));
@@ -542,7 +555,7 @@
         }
     };
     testServer.mOnListWriteEnd = [&](const ConcreteAttributePath & aPath, bool aWasSuccessful) {
-        NL_TEST_ASSERT(apSuite, onGoingPath == aPath);
+        EXPECT_EQ(onGoingPath, aPath);
         status.push_back(PathStatus(aPath, aWasSuccessful));
         onGoingPath = ConcreteAttributePath();
         ChipLogProgress(Zcl, "OnListWriteEnd endpoint=%u Cluster=" ChipLogFormatMEI " attribute=" ChipLogFormatMEI,
@@ -556,7 +569,7 @@
     {
         instructions.data = std::vector<ListData>(instructions.paths.size(), ListData::kList);
     }
-    NL_TEST_ASSERT(apSuite, instructions.paths.size() == instructions.data.size());
+    EXPECT_EQ(instructions.paths.size(), instructions.data.size());
 
     for (size_t i = 0; i < instructions.paths.size(); i++)
     {
@@ -579,22 +592,22 @@
             break;
         }
         }
-        NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+        EXPECT_EQ(err, CHIP_NO_ERROR);
     }
 
     err = writeClient->SendWriteRequest(sessionHandle);
-    NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
+    EXPECT_EQ(err, CHIP_NO_ERROR);
 
-    ctx.GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) +
-                                        System::Clock::Seconds16(1),
-                                    [&]() { return ctx.GetExchangeManager().GetNumActiveExchanges() == 0; });
+    pContext->GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) +
+                                              System::Clock::Seconds16(1),
+                                          [&]() { return pContext->GetExchangeManager().GetNumActiveExchanges() == 0; });
 
-    NL_TEST_ASSERT(apSuite, onGoingPath == app::ConcreteAttributePath());
-    NL_TEST_ASSERT(apSuite, status.size() == instructions.expectedStatus.size());
+    EXPECT_EQ(onGoingPath, app::ConcreteAttributePath());
+    EXPECT_EQ(status.size(), instructions.expectedStatus.size());
 
     for (size_t i = 0; i < status.size(); i++)
     {
-        NL_TEST_ASSERT(apSuite, status[i] == PathStatus(instructions.paths[i], instructions.expectedStatus[i]));
+        EXPECT_EQ(status[i], PathStatus(instructions.paths[i], instructions.expectedStatus[i]));
     }
 
     testServer.mOnListWriteBegin = nullptr;
@@ -603,12 +616,10 @@
 
 } // namespace TestTransactionalListInstructions
 
-void TestWriteChunking::TestTransactionalList(nlTestSuite * apSuite, void * apContext)
+TEST_F(TestWriteChunking, TestTransactionalList)
 {
     using namespace TestTransactionalListInstructions;
 
-    TestContext & ctx = *static_cast<TestContext *>(apContext);
-
     // Initialize the ember side server logic
     InitDataModelHandler();
 
@@ -620,7 +631,7 @@
 
     // Test 1: we should receive transaction notifications
     ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
                 .expectedStatus = { true },
@@ -628,7 +639,7 @@
 
     ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations");
     RunTest(
-        apSuite, ctx,
+        mpContext,
         Instructions{
             .paths                   = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
             .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; },
@@ -636,7 +647,7 @@
         });
 
     ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
                                     ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
@@ -644,7 +655,7 @@
             });
 
     ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
                            ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
@@ -662,7 +673,7 @@
     ChipLogProgress(Zcl,
                     "Test 5: transactional list callbacks will be called for nullable lists, test if it is handled correctly for "
                     "null value before non null values");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
                                     ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
@@ -673,7 +684,7 @@
     ChipLogProgress(Zcl,
                     "Test 6: transactional list callbacks will be called for nullable lists, test if it is handled correctly for "
                     "null value after non null values");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
                                     ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
@@ -684,7 +695,7 @@
     ChipLogProgress(Zcl,
                     "Test 7: transactional list callbacks will be called for nullable lists, test if it is handled correctly for "
                     "null value between non null values");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
                                     ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
@@ -694,7 +705,7 @@
             });
 
     ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
                 .data           = { ListData::kNull },
@@ -704,42 +715,16 @@
     ChipLogProgress(Zcl,
                     "Test 9: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal occurred "
                     "during processing the requests");
-    RunTest(apSuite, ctx,
+    RunTest(mpContext,
             Instructions{
                 .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
                 .data           = { ListData::kBadValue },
                 .expectedStatus = { false },
             });
 
-    NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0);
+    EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
 }
 
-const nlTest sTests[] = {
-    NL_TEST_DEF("TestListChunking", TestWriteChunking::TestListChunking),
-    NL_TEST_DEF("TestBadChunking", TestWriteChunking::TestBadChunking),
-    NL_TEST_DEF("TestConflictWrite", TestWriteChunking::TestConflictWrite),
-    NL_TEST_DEF("TestNonConflictWrite", TestWriteChunking::TestNonConflictWrite),
-    NL_TEST_DEF("TestTransactionalList", TestWriteChunking::TestTransactionalList),
-    NL_TEST_SENTINEL(),
-};
-
-nlTestSuite sSuite = {
-    "TestWriteChunking",
-    &sTests[0],
-    TestContext::nlTestSetUpTestSuite,
-    TestContext::nlTestTearDownTestSuite,
-    TestContext::nlTestSetUp,
-    TestContext::nlTestTearDown,
-};
-
 } // namespace
-
-int TestWriteChunkingTests()
-{
-    gSuite = &sSuite;
-    return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
-}
-
-CHIP_REGISTER_TEST_SUITE(TestWriteChunkingTests)