[WriteClient] Adding UnitTests for PutPreencodedAttribute and other  related methods (#38752)

* [WriteClient] Adding UnitTests for PutPreencodedAttribute and other related methods

* Integrating Comments

* Adding one more testcase in PreEncoded TLV

* deacativating TestWriteChunking for fake platform

* optimising: Putting repeeated testcases in a loop

* Integrating comments

* matching signs in assert

* adding maybe_unused to variable only used in progress logs
diff --git a/src/app/WriteClient.cpp b/src/app/WriteClient.cpp
index 74bddbf..8c57fed 100644
--- a/src/app/WriteClient.cpp
+++ b/src/app/WriteClient.cpp
@@ -247,7 +247,6 @@
     return err;
 }
 
-// TODO #38287 Add Unit Tests for PutPreencodedAttribute and for TryPutPreencodedAttributeWritePayloadIntoList.
 CHIP_ERROR WriteClient::PutPreencodedAttribute(const ConcreteDataAttributePath & attributePath, const TLV::TLVReader & data)
 {
     ReturnErrorOnFailure(EnsureMessage());
diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp
index 4736d02..6da2ffb 100644
--- a/src/app/tests/TestWriteInteraction.cpp
+++ b/src/app/tests/TestWriteInteraction.cpp
@@ -155,6 +155,12 @@
         AppContext::TearDown();
     }
 
+    enum class EncodingMethod
+    {
+        Standard,      // Encoding using WriteClient::EncodeAttribute()
+        PreencodedTLV, // Encoding using WriteClient::PutPreencodedAttribute()
+    };
+
     void TestWriteClient();
     void TestWriteClientGroup();
     void TestWriteHandlerReceiveInvalidMessage();
@@ -164,7 +170,7 @@
     void TestWriteInvalidMessage3();
     void TestWriteInvalidMessage4();
 
-    static void AddAttributeDataIB(WriteClient & aWriteClient);
+    static void AddAttributeDataIB(WriteClient & aWriteClient, EncodingMethod encoding);
     static void AddAttributeStatus(WriteHandler & aWriteHandler);
     static void GenerateWriteRequest(bool aIsTimedWrite, System::PacketBufferHandle & aPayload);
     static void GenerateWriteResponse(System::PacketBufferHandle & aPayload);
@@ -230,7 +236,7 @@
     CHIP_ERROR mError = CHIP_NO_ERROR;
 };
 
-void TestWriteInteraction::AddAttributeDataIB(WriteClient & aWriteClient)
+void TestWriteInteraction::AddAttributeDataIB(WriteClient & aWriteClient, EncodingMethod encoding = EncodingMethod::Standard)
 {
     AttributePathParams attributePathParams;
     bool attributeValue              = true;
@@ -238,7 +244,35 @@
     attributePathParams.mClusterId   = 3;
     attributePathParams.mAttributeId = 4;
 
-    EXPECT_EQ(aWriteClient.EncodeAttribute(attributePathParams, attributeValue), CHIP_NO_ERROR);
+    switch (encoding)
+    {
+    case EncodingMethod::Standard:
+
+        EXPECT_EQ(aWriteClient.EncodeAttribute(attributePathParams, attributeValue), CHIP_NO_ERROR);
+        break;
+
+    case EncodingMethod::PreencodedTLV:
+
+        // Encode AttributeData into TLV
+        uint8_t buffer[5];
+        TLV::TLVWriter writer;
+        writer.Init(buffer, sizeof(buffer));
+        TLV::TLVType outerContainer;
+
+        EXPECT_EQ(CHIP_NO_ERROR, writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerContainer));
+        EXPECT_EQ(CHIP_NO_ERROR, writer.PutBoolean(TLV::ContextTag(1), attributeValue));
+        EXPECT_EQ(CHIP_NO_ERROR, writer.EndContainer(outerContainer));
+
+        // Put Preencoded Data into AttributeDataIB
+        TLV::TLVReader reader;
+        reader.Init(buffer, writer.GetLengthWritten());
+        reader.Next();
+        EXPECT_EQ(aWriteClient.PutPreencodedAttribute(ConcreteDataAttributePath(attributePathParams.mEndpointId,
+                                                                                attributePathParams.mClusterId,
+                                                                                attributePathParams.mAttributeId),
+                                                      reader),
+                  CHIP_NO_ERROR);
+    }
 }
 
 void TestWriteInteraction::AddAttributeStatus(WriteHandler & aWriteHandler)
@@ -341,44 +375,49 @@
 TEST_F_FROM_FIXTURE(TestWriteInteraction, TestWriteClient)
 {
 
-    TestWriteClientCallback callback;
-    app::WriteClient writeClient(&GetExchangeManager(), &callback, /* aTimedWriteTimeoutMs = */ NullOptional);
+    for (EncodingMethod encoding : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
+    {
+        TestWriteClientCallback callback;
+        app::WriteClient writeClient(&GetExchangeManager(), &callback, /* aTimedWriteTimeoutMs = */ NullOptional);
 
-    System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
-    AddAttributeDataIB(writeClient);
+        System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
+        AddAttributeDataIB(writeClient, encoding);
 
-    EXPECT_EQ(writeClient.SendWriteRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
+        EXPECT_EQ(writeClient.SendWriteRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
 
-    DrainAndServiceIO();
+        DrainAndServiceIO();
 
-    GenerateWriteResponse(buf);
+        GenerateWriteResponse(buf);
 
-    EXPECT_EQ(writeClient.ProcessWriteResponseMessage(std::move(buf)), CHIP_NO_ERROR);
+        EXPECT_EQ(writeClient.ProcessWriteResponseMessage(std::move(buf)), CHIP_NO_ERROR);
 
-    writeClient.Close();
+        writeClient.Close();
 
-    Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr();
-    EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
+        Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr();
+        EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
+    }
 }
 
 TEST_F_FROM_FIXTURE(TestWriteInteraction, TestWriteClientGroup)
 {
+    for (EncodingMethod encodingMethod : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
+    {
+        TestWriteClientCallback callback;
+        app::WriteClient writeClient(&GetExchangeManager(), &callback, /* aTimedWriteTimeoutMs = */ NullOptional);
 
-    TestWriteClientCallback callback;
-    app::WriteClient writeClient(&GetExchangeManager(), &callback, /* aTimedWriteTimeoutMs = */ NullOptional);
+        System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
+        AddAttributeDataIB(writeClient, encodingMethod);
 
-    System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
-    AddAttributeDataIB(writeClient);
+        SessionHandle groupSession = GetSessionBobToFriends();
+        EXPECT_TRUE(groupSession->IsGroupSession());
 
-    SessionHandle groupSession = GetSessionBobToFriends();
-    EXPECT_TRUE(groupSession->IsGroupSession());
+        EXPECT_EQ(writeClient.SendWriteRequest(groupSession), CHIP_NO_ERROR);
 
-    EXPECT_EQ(writeClient.SendWriteRequest(groupSession), CHIP_NO_ERROR);
+        DrainAndServiceIO();
 
-    DrainAndServiceIO();
-
-    // The WriteClient should be shutdown once we SendWriteRequest for group.
-    EXPECT_EQ(writeClient.mState, WriteClient::State::AwaitingDestruction);
+        // The WriteClient should be shutdown once we SendWriteRequest for group.
+        EXPECT_EQ(writeClient.mState, WriteClient::State::AwaitingDestruction);
+    }
 }
 
 TEST_F(TestWriteInteraction, TestWriteHandler)
@@ -424,65 +463,95 @@
 TEST_F(TestWriteInteraction, TestWriteRoundtripWithClusterObjects)
 {
 
-    Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr();
-    // Shouldn't have anything in the retransmit table when starting the test.
-    EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
-
-    TestWriteClientCallback callback;
-    auto * engine = chip::app::InteractionModelEngine::GetInstance();
-    EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()), CHIP_NO_ERROR);
-
-    app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional<uint16_t>::Missing());
-
-    System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
-
-    AttributePathParams attributePathParams;
-    attributePathParams.mEndpointId  = 2;
-    attributePathParams.mClusterId   = 3;
-    attributePathParams.mAttributeId = 4;
-
-    const uint8_t byteSpanData[]     = { 0xde, 0xad, 0xbe, 0xef };
-    static const char charSpanData[] = "a simple test string";
-
-    app::Clusters::UnitTesting::Structs::SimpleStruct::Type dataTx;
-    dataTx.a = 12;
-    dataTx.b = true;
-    dataTx.d = chip::ByteSpan(byteSpanData);
-    // Spec A.11.2 strings SHALL NOT include a terminating null character to mark the end of a string.
-    dataTx.e = chip::Span<const char>(charSpanData, strlen(charSpanData));
-
-    EXPECT_EQ(writeClient.EncodeAttribute(attributePathParams, dataTx), CHIP_NO_ERROR);
-
-    EXPECT_EQ(callback.mOnSuccessCalled, 0);
-
-    EXPECT_EQ(writeClient.SendWriteRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
-
-    DrainAndServiceIO();
-
-    EXPECT_EQ(callback.mOnSuccessCalled, 1);
-
+    for (EncodingMethod encoding : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
     {
-        app::Clusters::UnitTesting::Structs::SimpleStruct::Type dataRx;
-        TLV::TLVReader reader;
-        reader.Init(chip::Test::attributeDataTLV, chip::Test::attributeDataTLVLen);
-        reader.Next();
-        EXPECT_EQ(CHIP_NO_ERROR, DataModel::Decode(reader, dataRx));
-        EXPECT_EQ(dataRx.a, dataTx.a);
-        EXPECT_EQ(dataRx.b, dataTx.b);
-        EXPECT_TRUE(dataRx.d.data_equal(dataTx.d));
-        // Equals to dataRx.e.size() == dataTx.e.size() && memncmp(dataRx.e.data(), dataTx.e.data(), dataTx.e.size()) == 0
-        EXPECT_TRUE(dataRx.e.data_equal(dataTx.e));
+        Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr();
+        // Shouldn't have anything in the retransmit table when starting the test.
+        EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
+
+        TestWriteClientCallback callback;
+        auto * engine = chip::app::InteractionModelEngine::GetInstance();
+        EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()),
+                  CHIP_NO_ERROR);
+
+        app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional<uint16_t>::Missing());
+
+        System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
+
+        AttributePathParams attributePathParams;
+        attributePathParams.mEndpointId  = 2;
+        attributePathParams.mClusterId   = 3;
+        attributePathParams.mAttributeId = 4;
+
+        const uint8_t byteSpanData[]     = { 0xde, 0xad, 0xbe, 0xef };
+        static const char charSpanData[] = "a simple test string";
+
+        app::Clusters::UnitTesting::Structs::SimpleStruct::Type dataTx;
+        dataTx.a = 12;
+        dataTx.b = true;
+        dataTx.d = chip::ByteSpan(byteSpanData);
+        // Spec A.11.2 strings SHALL NOT include a terminating null character to mark the end of a string.
+        dataTx.e = chip::Span<const char>(charSpanData, strlen(charSpanData));
+
+        if (encoding == EncodingMethod::Standard)
+        {
+            EXPECT_EQ(writeClient.EncodeAttribute(attributePathParams, dataTx), CHIP_NO_ERROR);
+        }
+        else if (encoding == EncodingMethod::PreencodedTLV)
+        {
+            // Encode AttributeData into TLV
+            uint8_t buffer[50];
+            TLV::TLVWriter writer;
+            writer.Init(buffer, sizeof(buffer));
+            TLV::TLVType outerContainer;
+
+            EXPECT_EQ(CHIP_NO_ERROR, writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerContainer));
+            EXPECT_EQ(CHIP_NO_ERROR, writer.Put(TLV::ContextTag(0), dataTx.a));
+            EXPECT_EQ(CHIP_NO_ERROR, writer.PutBoolean(TLV::ContextTag(1), dataTx.b));
+            EXPECT_EQ(CHIP_NO_ERROR, writer.Put(TLV::ContextTag(3), dataTx.d));
+            EXPECT_EQ(CHIP_NO_ERROR, writer.PutString(TLV::ContextTag(4), dataTx.e));
+            EXPECT_EQ(CHIP_NO_ERROR, writer.EndContainer(outerContainer));
+
+            // Put Preencoded Data into AttributeDataIB
+            TLV::TLVReader dataTxTLV;
+            dataTxTLV.Init(buffer, writer.GetLengthWritten());
+            dataTxTLV.Next();
+            ConcreteDataAttributePath path = ConcreteDataAttributePath(
+                attributePathParams.mEndpointId, attributePathParams.mClusterId, attributePathParams.mAttributeId);
+            EXPECT_EQ(writeClient.PutPreencodedAttribute(path, dataTxTLV), CHIP_NO_ERROR);
+        }
+
+        EXPECT_EQ(callback.mOnSuccessCalled, 0);
+
+        EXPECT_EQ(writeClient.SendWriteRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
+
+        DrainAndServiceIO();
+
+        EXPECT_EQ(callback.mOnSuccessCalled, 1);
+
+        {
+            app::Clusters::UnitTesting::Structs::SimpleStruct::Type dataRx;
+            TLV::TLVReader reader;
+            reader.Init(chip::Test::attributeDataTLV, chip::Test::attributeDataTLVLen);
+            reader.Next();
+            EXPECT_EQ(CHIP_NO_ERROR, DataModel::Decode(reader, dataRx));
+            EXPECT_EQ(dataRx.a, dataTx.a);
+            EXPECT_EQ(dataRx.b, dataTx.b);
+            EXPECT_TRUE(dataRx.d.data_equal(dataTx.d));
+            // Equals to dataRx.e.size() == dataTx.e.size() && memncmp(dataRx.e.data(), dataTx.e.data(), dataTx.e.size()) == 0
+            EXPECT_TRUE(dataRx.e.data_equal(dataTx.e));
+        }
+
+        EXPECT_EQ(callback.mOnSuccessCalled, 1);
+        EXPECT_EQ(callback.mOnErrorCalled, 0);
+        EXPECT_EQ(callback.mOnDoneCalled, 1);
+
+        // By now we should have closed all exchanges and sent all pending acks, so
+        // there should be no queued-up things in the retransmit table.
+        EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
+
+        engine->Shutdown();
     }
-
-    EXPECT_EQ(callback.mOnSuccessCalled, 1);
-    EXPECT_EQ(callback.mOnErrorCalled, 0);
-    EXPECT_EQ(callback.mOnDoneCalled, 1);
-
-    // By now we should have closed all exchanges and sent all pending acks, so
-    // there should be no queued-up things in the retransmit table.
-    EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
-
-    engine->Shutdown();
 }
 
 TEST_F(TestWriteInteraction, TestWriteRoundtripWithClusterObjectsVersionMatch)
@@ -578,36 +647,39 @@
 
 TEST_F(TestWriteInteraction, TestWriteRoundtrip)
 {
+    for (EncodingMethod encodingMethod : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
+    {
+        Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr();
+        // Shouldn't have anything in the retransmit table when starting the test.
+        EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
 
-    Messaging::ReliableMessageMgr * rm = GetExchangeManager().GetReliableMessageMgr();
-    // Shouldn't have anything in the retransmit table when starting the test.
-    EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
+        TestWriteClientCallback callback;
+        auto * engine = chip::app::InteractionModelEngine::GetInstance();
+        EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()),
+                  CHIP_NO_ERROR);
 
-    TestWriteClientCallback callback;
-    auto * engine = chip::app::InteractionModelEngine::GetInstance();
-    EXPECT_EQ(engine->Init(&GetExchangeManager(), &GetFabricTable(), app::reporting::GetDefaultReportScheduler()), CHIP_NO_ERROR);
+        app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional<uint16_t>::Missing());
 
-    app::WriteClient writeClient(engine->GetExchangeManager(), &callback, Optional<uint16_t>::Missing());
+        System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
+        AddAttributeDataIB(writeClient, encodingMethod);
 
-    System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
-    AddAttributeDataIB(writeClient);
+        EXPECT_EQ(callback.mOnSuccessCalled, 0);
+        EXPECT_EQ(callback.mOnErrorCalled, 0);
+        EXPECT_EQ(callback.mOnDoneCalled, 0);
 
-    EXPECT_EQ(callback.mOnSuccessCalled, 0);
-    EXPECT_EQ(callback.mOnErrorCalled, 0);
-    EXPECT_EQ(callback.mOnDoneCalled, 0);
+        EXPECT_EQ(writeClient.SendWriteRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
 
-    EXPECT_EQ(writeClient.SendWriteRequest(GetSessionBobToAlice()), CHIP_NO_ERROR);
+        DrainAndServiceIO();
 
-    DrainAndServiceIO();
+        EXPECT_EQ(callback.mOnSuccessCalled, 1);
+        EXPECT_EQ(callback.mOnErrorCalled, 0);
+        EXPECT_EQ(callback.mOnDoneCalled, 1);
+        // By now we should have closed all exchanges and sent all pending acks, so
+        // there should be no queued-up things in the retransmit table.
+        EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
 
-    EXPECT_EQ(callback.mOnSuccessCalled, 1);
-    EXPECT_EQ(callback.mOnErrorCalled, 0);
-    EXPECT_EQ(callback.mOnDoneCalled, 1);
-    // By now we should have closed all exchanges and sent all pending acks, so
-    // there should be no queued-up things in the retransmit table.
-    EXPECT_EQ(rm->TestGetCountRetransTable(), 0);
-
-    engine->Shutdown();
+        engine->Shutdown();
+    }
 }
 
 // This test creates a chunked write request, we drop the second write chunk message, then write handler receives unknown
diff --git a/src/controller/tests/BUILD.gn b/src/controller/tests/BUILD.gn
index 69c9e24..2ccd960 100644
--- a/src/controller/tests/BUILD.gn
+++ b/src/controller/tests/BUILD.gn
@@ -35,9 +35,16 @@
       "TestEventNumberCaching.cpp",
       "TestReadChunking.cpp",
       "TestServerCommandDispatch.cpp",
-      "TestWriteChunking.cpp",
     ]
 
+    # At some instances, this Test end ups allocating more than 32 Timers, and since the fake platform doesn`t use the heap, the maximum number of allowed timers is 32.
+    # So we get failures because we can't allocate any more timers, in the log it looks like:
+    # SendMessage() to UDP:[::1]:5541 failed: b
+    # deactivating since the Fake platform is mostly done to test Platform APIs
+    if (chip_device_platform != "fake") {
+      test_sources += [ "TestWriteChunking.cpp" ]
+    }
+
     # Not supported on efr32.
     if (chip_device_platform != "efr32") {
       test_sources += [
diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp
index 105c9e8..5bb038c 100644
--- a/src/controller/tests/TestWriteChunking.cpp
+++ b/src/controller/tests/TestWriteChunking.cpp
@@ -81,6 +81,12 @@
         kBadValue,
     };
 
+    enum class EncodingMethod
+    {
+        Standard,      // Encoding using WriteClient::EncodeAttribute()
+        PreencodedTLV, // Encoding using WriteClient::PutPreencodedAttribute()
+    };
+
     struct Instructions
     {
         // The paths used in write request
@@ -93,10 +99,59 @@
         std::vector<bool> expectedStatus;
     };
 
-    void RunTest(Instructions instructions);
-    void RunTest_NonEmptyReplaceAll(Instructions instructions);
+    void RunTest(Instructions instructions, EncodingMethod encodingMethod);
+    void RunTest_NonEmptyReplaceAll(Instructions instructions, EncodingMethod encodingMethod);
+
+    template <class T>
+    void EncodeAttributeListIntoTLV(const DataModel::List<T> & aListAttribute, TLV::ScopedBufferTLVReader & outTlvReader);
 };
 
+// Encodes an attribute of List Data Type into a TLV Reader object for testing WriteClient::PutPreencodedAttribute
+// Warning: This method only encodes uint8_t or ByteSpans whose length fits in one octet
+template <class T>
+void TestWriteChunking::EncodeAttributeListIntoTLV(const DataModel::List<T> & aListAttribute,
+                                                   TLV::ScopedBufferTLVReader & outEncodedListTlvReader)
+{
+    static_assert(std::is_same<T, chip::ByteSpan>::value || std::is_same<T, uint8_t>::value,
+                  "This method only encodes uint8_t or ByteSpans whose length fits in one octet");
+
+    size_t estimatedSize = 0;
+    for (size_t i = 0; i < aListAttribute.size(); i++)
+    {
+        if constexpr (std::is_same<T, uint8_t>::value)
+        {
+            // Control Octet (1) + size of uint8_t
+            estimatedSize += 1 + sizeof(uint8_t);
+        }
+        else if constexpr (std::is_same<T, chip::ByteSpan>::value)
+        {
+            ASSERT_LE(aListAttribute[i].size(), static_cast<size_t>(UINT8_MAX));
+
+            // Control Octet (1) + Length Octet (1) + size of a single ByteSpan
+            estimatedSize += 2 + aListAttribute[i].size();
+        }
+    }
+
+    // Encode AttributeData into a TLV Array
+    chip::Platform::ScopedMemoryBufferWithSize<uint8_t> buffer;
+    buffer.Alloc(TLV::EstimateStructOverhead(estimatedSize));
+
+    TLV::TLVWriter writer;
+    writer.Init(buffer.Get(), buffer.AllocatedSize());
+    TLV::TLVType outerContainer;
+
+    EXPECT_EQ(CHIP_NO_ERROR, writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, outerContainer));
+    for (auto & item : aListAttribute)
+    {
+        EXPECT_EQ(CHIP_NO_ERROR, writer.Put(TLV::AnonymousTag(), item));
+    }
+    EXPECT_EQ(CHIP_NO_ERROR, writer.EndContainer(outerContainer));
+
+    // Move Encoded TLV Array into TLVReader Object
+    outEncodedListTlvReader.Init(std::move(buffer), writer.GetLengthWritten());
+    outEncodedListTlvReader.Next();
+}
+
 //clang-format off
 
 DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint)
@@ -300,48 +355,67 @@
     // to ensure we'll sweep from fitting 2 chunks to 3-4 chunks.
     //
     constexpr size_t minReservationSize = kMaxSecureSduLengthBytes - 75 - 100;
-    for (uint32_t i = 100; i > 0; i--)
+
+    for (EncodingMethod encodingMethod : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
     {
-        CHIP_ERROR err = CHIP_NO_ERROR;
-        TestWriteCallback writeCallback;
 
-        ChipLogDetail(DataManagement, "Running iteration %d\n", static_cast<int>(i));
-
-        gIterationCount = i;
-
-        app::WriteClient writeClient(&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));
-        EXPECT_EQ(err, CHIP_NO_ERROR);
-
-        err = writeClient.SendWriteRequest(sessionHandle);
-        EXPECT_EQ(err, CHIP_NO_ERROR);
-
-        //
-        // Service the IO + Engine till we get a ReportEnd callback on the client.
-        // Since bugs can happen, we don't want this test to never stop, so create a ceiling for how many
-        // times this can run without seeing expected results.
-        //
-        for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
+        for (uint32_t i = 100; i > 0; i--)
         {
-            DrainAndServiceIO();
-        }
+            CHIP_ERROR err = CHIP_NO_ERROR;
+            TestWriteCallback writeCallback;
 
-        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);
+            ChipLogDetail(DataManagement, "Running iteration %d\n", static_cast<int>(i));
 
-        EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
+            gIterationCount = i;
 
-        //
-        // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
-        //
-        if (HasFailure())
-        {
-            break;
+            app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional<uint16_t>::Missing(),
+                                         static_cast<uint16_t>(minReservationSize + i) /* reserved buffer size */);
+
+            ByteSpan list[kTestListLength];
+
+            if (encodingMethod == EncodingMethod::Standard)
+            {
+                err = writeClient.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength));
+                EXPECT_EQ(err, CHIP_NO_ERROR);
+            }
+            else if (encodingMethod == EncodingMethod::PreencodedTLV)
+            {
+                TLV::ScopedBufferTLVReader encodedListTLV;
+                EncodeAttributeListIntoTLV(DataModel::List<ByteSpan>(list, kTestListLength), encodedListTLV);
+
+                ConcreteDataAttributePath path =
+                    ConcreteDataAttributePath(attributePath.mEndpointId, attributePath.mClusterId, attributePath.mAttributeId);
+                EXPECT_EQ(writeClient.PutPreencodedAttribute(path, encodedListTLV), CHIP_NO_ERROR);
+            }
+
+            //
+
+            err = writeClient.SendWriteRequest(sessionHandle);
+            EXPECT_EQ(err, CHIP_NO_ERROR);
+
+            //
+            // Service the IO + Engine till we get a ReportEnd callback on the client.
+            // Since bugs can happen, we don't want this test to never stop, so create a ceiling for how many
+            // times this can run without seeing expected results.
+            //
+            for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
+            {
+                DrainAndServiceIO();
+            }
+
+            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);
+
+            EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
+
+            //
+            // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
+            //
+            if (HasFailure())
+            {
+                break;
+            }
         }
     }
     emberAfClearDynamicEndpoint(0);
@@ -377,59 +451,75 @@
     // Start with a high reservation (maxReservationSize) to force chunking, then decrease the reservation in 1-byte steps.
     // This increases the buffer space available for encoding, gradually reducing the need for chunking, until chunking would not
     // occur anymore. This helps validate various edge cases.
-    for (uint32_t reservationReduction = 0; reservationReduction < 40; reservationReduction++)
+
+    for (EncodingMethod encodingMethod : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
     {
-        CHIP_ERROR err = CHIP_NO_ERROR;
-        TestWriteCallback writeCallback;
-
-        ChipLogDetail(DataManagement, "Running iteration %d\n", static_cast<int>(reservationReduction));
-
-        app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, NullOptional,
-                                     static_cast<uint16_t>(maxReservationSize - reservationReduction) /* reserved buffer size */);
-
-        ByteSpan list[kTestListLength2];
-
-        err = writeClient.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength2));
-
-        EXPECT_EQ(err, CHIP_NO_ERROR);
-
-        // Ensure that chunking actually occurred in the first iteration. We will iteratively chunk less and less, until chunking
-        // would not occur anymore. Thus, this check is only needed at start.
-        if (reservationReduction == 0)
+        for (uint32_t reservationReduction = 0; reservationReduction < 40; reservationReduction++)
         {
-            ASSERT_TRUE(writeClient.IsWriteRequestChunked());
-        }
+            CHIP_ERROR err = CHIP_NO_ERROR;
+            TestWriteCallback writeCallback;
 
-        err = writeClient.SendWriteRequest(sessionHandle);
-        EXPECT_EQ(err, CHIP_NO_ERROR);
+            ChipLogDetail(DataManagement, "Running iteration %d\n", static_cast<int>(reservationReduction));
 
-        //
-        // Service the IO + Engine till we get a ReportEnd callback on the client.
-        // Since bugs can happen, we don't want this test to never stop, so create a ceiling for how many
-        // times this can run without seeing expected results.
-        //
-        for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
-        {
-            DrainAndServiceIO();
-        }
+            app::WriteClient writeClient(
+                &GetExchangeManager(), &writeCallback, NullOptional,
+                static_cast<uint16_t>(maxReservationSize - reservationReduction) /* reserved buffer size */);
 
-        // Due to Write Chunking being done dynamically (fitting as many items as possible into an initial ReplaceAll List, before
-        // starting to chunk), it is fragile to try to predict mSuccessCount. It all depends on how much was packed into the initial
-        // ReplaceAll List.
-        // However, we know for sure that writeCallback should NEVER fail.
-        EXPECT_EQ(writeCallback.mErrorCount, 0u);
-        EXPECT_EQ(writeCallback.mOnDoneCount, 1u);
+            ByteSpan list[kTestListLength2];
 
-        EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
+            if (encodingMethod == EncodingMethod::Standard)
+            {
+                err = writeClient.EncodeAttribute(attributePath, app::DataModel::List<ByteSpan>(list, kTestListLength2));
+                EXPECT_EQ(err, CHIP_NO_ERROR);
+            }
+            else if (encodingMethod == EncodingMethod::PreencodedTLV)
+            {
+                TLV::ScopedBufferTLVReader encodedListTLV;
+                EncodeAttributeListIntoTLV(DataModel::List<ByteSpan>(list, kTestListLength2), encodedListTLV);
 
-        //
-        // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
-        //
-        if (HasFailure())
-        {
-            break;
+                ConcreteDataAttributePath path =
+                    ConcreteDataAttributePath(attributePath.mEndpointId, attributePath.mClusterId, attributePath.mAttributeId);
+                EXPECT_EQ(writeClient.PutPreencodedAttribute(path, encodedListTLV), CHIP_NO_ERROR);
+            }
+
+            // Ensure that chunking actually occurred in the first iteration. We will iteratively chunk less and less, until
+            // chunking would not occur anymore. Thus, this check is only needed at start.
+            if (reservationReduction == 0)
+            {
+                ASSERT_TRUE(writeClient.IsWriteRequestChunked());
+            }
+
+            err = writeClient.SendWriteRequest(sessionHandle);
+            EXPECT_EQ(err, CHIP_NO_ERROR);
+
+            //
+            // Service the IO + Engine till we get a ReportEnd callback on the client.
+            // Since bugs can happen, we don't want this test to never stop, so create a ceiling for how many
+            // times this can run without seeing expected results.
+            //
+            for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++)
+            {
+                DrainAndServiceIO();
+            }
+
+            // Due to Write Chunking being done dynamically (fitting as many items as possible into an initial ReplaceAll List,
+            // before starting to chunk), it is fragile to try to predict mSuccessCount. It all depends on how much was packed into
+            // the initial ReplaceAll List. However, we know for sure that writeCallback should NEVER fail.
+            EXPECT_EQ(writeCallback.mErrorCount, 0u);
+            EXPECT_EQ(writeCallback.mOnDoneCount, 1u);
+
+            EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
+
+            //
+            // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs.
+            //
+            if (HasFailure())
+            {
+                break;
+            }
         }
     }
+
     emberAfClearDynamicEndpoint(0);
 }
 
@@ -898,8 +988,9 @@
 
     emberAfClearDynamicEndpoint(0);
 }
+// for (EncodingMethod encodingMethod : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
 
-void TestWriteChunking::RunTest(Instructions instructions)
+void TestWriteChunking::RunTest(Instructions instructions, EncodingMethod encodingMethod = EncodingMethod::Standard)
 {
     CHIP_ERROR err     = CHIP_NO_ERROR;
     auto sessionHandle = GetSessionBobToAlice();
@@ -958,13 +1049,38 @@
             break;
         }
         case ListData::kList: {
-            err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
-                                               DataModel::List<ByteSpan>(list, kTestListLength));
+
+            if (encodingMethod == EncodingMethod::Standard)
+            {
+                err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
+                                                   DataModel::List<ByteSpan>(list, kTestListLength));
+            }
+            else if (encodingMethod == EncodingMethod::PreencodedTLV)
+            {
+                TLV::ScopedBufferTLVReader encodedListTLV;
+                EncodeAttributeListIntoTLV(DataModel::List<ByteSpan>(list, kTestListLength), encodedListTLV);
+
+                ConcreteDataAttributePath path = ConcreteDataAttributePath(p.mEndpointId, p.mClusterId, p.mAttributeId);
+                err                            = writeClient->PutPreencodedAttribute(path, encodedListTLV);
+            }
             break;
         }
         case ListData::kBadValue: {
-            err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
-                                               DataModel::List<uint8_t>(badList, kTestListLength));
+
+            if (encodingMethod == EncodingMethod::Standard)
+            {
+                err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
+                                                   DataModel::List<uint8_t>(badList, kTestListLength));
+            }
+            else if (encodingMethod == EncodingMethod::PreencodedTLV)
+            {
+                TLV::ScopedBufferTLVReader encodedListTLV;
+                EncodeAttributeListIntoTLV(DataModel::List<uint8_t>(badList, kTestListLength), encodedListTLV);
+
+                ConcreteDataAttributePath path = ConcreteDataAttributePath(p.mEndpointId, p.mClusterId, p.mAttributeId);
+                err                            = writeClient->PutPreencodedAttribute(path, encodedListTLV);
+            }
+
             break;
         }
         }
@@ -1003,89 +1119,121 @@
     // Register our fake attribute access interface.
     AttributeAccessInterfaceRegistry::Instance().Register(&testServer);
 
-    // Test 1: we should receive transaction notifications
-    ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications");
-    RunTest(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
-        .expectedStatus = { true },
-    });
+    for (EncodingMethod encodingMethod : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
+    {
+        // For builds without ChipLogProgress, encodingMethodName will be ununsed and trigger build failures
+        [[maybe_unused]] const char * encodingMethodName =
+            (encodingMethod == EncodingMethod::Standard ? "StandardEncoding" : "PreencodedTLV");
 
-    ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations");
-    RunTest(Instructions{
-        .paths                   = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
-        .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; },
-        .expectedStatus          = { false },
-    });
-
-    ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction");
-    RunTest(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
-                            ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
-        .expectedStatus = { true, true },
-    });
-
-    ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list");
-    RunTest(Instructions{
-        .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
-                   ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
-        .onListWriteBeginActions =
-            [&](const app::ConcreteAttributePath & aPath) {
-                if (aPath.mAttributeId == kTestListAttribute2)
-                {
-                    return Operations::kShutdownWriteClient;
-                }
-                return Operations::kNoop;
+        // Test 1: we should receive transaction notifications
+        ChipLogProgress(Zcl, "Test 1 [%s]: we should receive transaction notifications", encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
+                .expectedStatus = { true },
             },
-        .expectedStatus = { true, false },
-    });
+            encodingMethod);
 
-    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(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
-                            ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
-        .data           = { ListData::kNull, ListData::kList },
-        .expectedStatus = { true },
-    });
+        ChipLogProgress(Zcl, "Test 2a [%s]: we should receive transaction notifications for incomplete list operations",
+                        encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
+                .onListWriteBeginActions =
+                    [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; },
+                .expectedStatus = { false },
+            },
+            encodingMethod);
 
-    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(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
-                            ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
-        .data           = { ListData::kList, ListData::kNull },
-        .expectedStatus = { true },
-    });
+        ChipLogProgress(Zcl, "Test 3 [%s]: we should receive transaction notifications for every list in the transaction",
+                        encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
+                                    ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
+                .expectedStatus = { true, true },
+            },
+            encodingMethod);
 
-    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(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
-                            ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
-                            ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
-        .data           = { ListData::kList, ListData::kNull, ListData::kList },
-        .expectedStatus = { true },
-    });
+        ChipLogProgress(Zcl, "Test 4 [%s]: we should receive transaction notifications with the status of each list",
+                        encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
+                           ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) },
+                .onListWriteBeginActions =
+                    [&](const app::ConcreteAttributePath & aPath) {
+                        if (aPath.mAttributeId == kTestListAttribute2)
+                        {
+                            return Operations::kShutdownWriteClient;
+                        }
+                        return Operations::kNoop;
+                    },
+                .expectedStatus = { true, false },
+            },
+            encodingMethod);
 
-    ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists");
-    RunTest(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
-        .data           = { ListData::kNull },
-        .expectedStatus = { true },
-    });
+        ChipLogProgress(Zcl,
+                        "Test 5 [%s]: transactional list callbacks will be called for nullable lists, test if it is handled "
+                        "correctly for null value before non null values",
+                        encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
+                                    ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
+                .data           = { ListData::kNull, ListData::kList },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
 
-    ChipLogProgress(Zcl,
-                    "Test 9: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal occurred "
-                    "during processing the requests");
-    RunTest(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
-        .data           = { ListData::kBadValue },
-        .expectedStatus = { false },
-    });
+        ChipLogProgress(Zcl,
+                        "Test 6 [%s]: transactional list callbacks will be called for nullable lists, test if it is handled "
+                        "correctly for null value after non null values",
+                        encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
+                                    ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
+                .data           = { ListData::kList, ListData::kNull },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
 
+        ChipLogProgress(Zcl,
+                        "Test 7 [%s]: transactional list callbacks will be called for nullable lists, test if it is handled "
+                        "correctly for null value between non null values",
+                        encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
+                                    ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute),
+                                    ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
+                .data           = { ListData::kList, ListData::kNull, ListData::kList },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl, "Test 8 [%s]: transactional list callbacks will be called for nullable lists", encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
+                .data           = { ListData::kNull },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl,
+                        "Test 9 [%s]: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal "
+                        "occurred during processing the requests",
+                        encodingMethodName);
+        RunTest(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) },
+                .data           = { ListData::kBadValue },
+                .expectedStatus = { false },
+            },
+            encodingMethod);
+    }
     EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);
@@ -1095,7 +1243,8 @@
  * A Variant of RunTest above, that tests the Code Path where we encode a Non-Replace All List in WriteRequests, this
  * happens with the ACL Cluster.
  */
-void TestWriteChunking::RunTest_NonEmptyReplaceAll(Instructions instructions)
+void TestWriteChunking::RunTest_NonEmptyReplaceAll(Instructions instructions,
+                                                   EncodingMethod encodingMethod = EncodingMethod::Standard)
 {
     CHIP_ERROR err     = CHIP_NO_ERROR;
     auto sessionHandle = GetSessionBobToAlice();
@@ -1156,13 +1305,38 @@
             break;
         }
         case ListData::kList: {
-            err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
-                                               DataModel::List<ByteSpan>(list, kTestListLength2));
+
+            if (encodingMethod == EncodingMethod::Standard)
+            {
+                err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
+                                                   DataModel::List<ByteSpan>(list, kTestListLength2));
+            }
+            else if (encodingMethod == EncodingMethod::PreencodedTLV)
+            {
+                TLV::ScopedBufferTLVReader encodedListTLV;
+                EncodeAttributeListIntoTLV(DataModel::List<ByteSpan>(list, kTestListLength2), encodedListTLV);
+
+                ConcreteDataAttributePath path = ConcreteDataAttributePath(p.mEndpointId, p.mClusterId, p.mAttributeId);
+                err                            = writeClient->PutPreencodedAttribute(path, encodedListTLV);
+            }
             break;
         }
         case ListData::kBadValue: {
-            err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
-                                               DataModel::List<uint8_t>(badList, kTestListLength2));
+
+            if (encodingMethod == EncodingMethod::Standard)
+            {
+                err = writeClient->EncodeAttribute(AttributePathParams(p.mEndpointId, p.mClusterId, p.mAttributeId),
+                                                   DataModel::List<uint8_t>(badList, kTestListLength2));
+            }
+            else if (encodingMethod == EncodingMethod::PreencodedTLV)
+            {
+                TLV::ScopedBufferTLVReader encodedListTLV;
+                EncodeAttributeListIntoTLV(DataModel::List<uint8_t>(badList, kTestListLength2), encodedListTLV);
+
+                ConcreteDataAttributePath path = ConcreteDataAttributePath(p.mEndpointId, p.mClusterId, p.mAttributeId);
+                err                            = writeClient->PutPreencodedAttribute(path, encodedListTLV);
+            }
+
             break;
         }
         }
@@ -1201,113 +1375,146 @@
     // Register our fake attribute access interface.
     AttributeAccessInterfaceRegistry::Instance().Register(&testServerAcl);
 
-    // Test 1: we should receive transaction notifications
-    ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications");
-    RunTest_NonEmptyReplaceAll(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
-        .expectedStatus = { true },
-    });
-
-    ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations");
-    RunTest_NonEmptyReplaceAll(Instructions{
-        .paths = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
-        .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; },
-        .expectedStatus          = { false },
-    });
-
-    ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction");
-    RunTest_NonEmptyReplaceAll(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
-                            ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Extension::Id) },
-        .expectedStatus = { true, true },
-    });
-
-    ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list");
-    RunTest_NonEmptyReplaceAll(Instructions{
-        .paths = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
-                   ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Extension::Id) },
-        .onListWriteBeginActions =
-            [&](const app::ConcreteAttributePath & aPath) {
-                if (aPath.mAttributeId == AccessControl::Attributes::Extension::Id)
-                {
-                    return Operations::kShutdownWriteClient;
-                }
-                return Operations::kNoop;
-            },
-        .expectedStatus = { true, false },
-    });
-
-    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_NonEmptyReplaceAll(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
-                            ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
-        .data           = { ListData::kNull, ListData::kList },
-        .expectedStatus = { true },
-    });
-
-    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_NonEmptyReplaceAll(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
-                            ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
-        .data           = { ListData::kList, ListData::kNull },
-        .expectedStatus = { true },
-    });
-
-    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_NonEmptyReplaceAll(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
-                            ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
-                            ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
-        .data           = { ListData::kList, ListData::kNull, ListData::kList },
-        .expectedStatus = { true },
-    });
-
-    ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists");
-    RunTest_NonEmptyReplaceAll(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
-        .data           = { ListData::kNull },
-        .expectedStatus = { true },
-    });
-
-    ChipLogProgress(Zcl,
-                    "Test 9: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal occurred "
-                    "during processing the requests");
-    RunTest_NonEmptyReplaceAll(Instructions{
-        .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
-        .data           = { ListData::kBadValue },
-        .expectedStatus = { false },
-    });
-
-    // This TestCase tests corner cases when we Encode many attributes into the same WriteRequest, up to 10 Attributes will be
-    // Encoded.
-    for (int nullableListCount = 1; nullableListCount <= 10; nullableListCount++)
+    for (EncodingMethod encodingMethod : { EncodingMethod::Standard, EncodingMethod::PreencodedTLV })
     {
-        ChipLogProgress(Zcl, "Test 10.%d: Encoding %d nullable lists following a single non-nullable list", nullableListCount,
-                        nullableListCount);
+        // For builds without ChipLogProgress, encodingMethodName will be ununsed and trigger build failures
+        [[maybe_unused]] const char * encodingMethodName =
+            (encodingMethod == EncodingMethod::Standard ? "StandardEncoding" : "PreencodedTLV");
 
-        Instructions test;
+        // Test 1: we should receive transaction notifications
+        ChipLogProgress(Zcl, "Test 1 [%s]: we should receive transaction notifications", encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
 
-        // Add the single non-nullable list
-        test.paths.push_back(ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id));
-        test.data.push_back(ListData::kList);
+        ChipLogProgress(Zcl, "Test 2 [%s]: we should receive transaction notifications for incomplete list operations",
+                        encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
+                .onListWriteBeginActions =
+                    [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; },
+                .expectedStatus = { false },
+            },
+            encodingMethod);
 
-        // Add the nullable lists
-        for (int i = 0; i < nullableListCount; i++)
+        ChipLogProgress(Zcl, "Test 3 [%s]: we should receive transaction notifications for every list in the transaction",
+                        encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
+                                    ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Extension::Id) },
+                .expectedStatus = { true, true },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl, "Test 4 [%s]: we should receive transaction notifications with the status of each list",
+                        encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
+                           ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Extension::Id) },
+                .onListWriteBeginActions =
+                    [&](const app::ConcreteAttributePath & aPath) {
+                        if (aPath.mAttributeId == AccessControl::Attributes::Extension::Id)
+                        {
+                            return Operations::kShutdownWriteClient;
+                        }
+                        return Operations::kNoop;
+                    },
+                .expectedStatus = { true, false },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl,
+                        "Test 5 [%s]: transactional list callbacks will be called for nullable lists, test if it is handled "
+                        "correctly for null value before non null values",
+                        encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
+                                    ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
+                .data           = { ListData::kNull, ListData::kList },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl,
+                        "Test 6 [%s]: transactional list callbacks will be called for nullable lists, test if it is handled "
+                        "correctly for null value after non null values",
+                        encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
+                                    ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
+                .data           = { ListData::kList, ListData::kNull },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl,
+                        "Test 7 [%s]: transactional list callbacks will be called for nullable lists, test if it is handled "
+                        "correctly for null value between non null values",
+                        encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
+                                    ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id),
+                                    ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
+                .data           = { ListData::kList, ListData::kNull, ListData::kList },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl, "Test 8 [%s]: transactional list callbacks will be called for nullable lists", encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
+                .data           = { ListData::kNull },
+                .expectedStatus = { true },
+            },
+            encodingMethod);
+
+        ChipLogProgress(Zcl,
+                        "Test 9 [%s]: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal "
+                        "occurred during processing the requests",
+                        encodingMethodName);
+        RunTest_NonEmptyReplaceAll(
+            Instructions{
+                .paths          = { ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id) },
+                .data           = { ListData::kBadValue },
+                .expectedStatus = { false },
+            },
+            encodingMethod);
+
+        // This TestCase tests corner cases when we Encode many attributes into the same WriteRequest, up to 10 Attributes will be
+        // Encoded.
+
+        for (int nullableListCount = 1; nullableListCount <= 10; nullableListCount++)
         {
+            ChipLogProgress(Zcl, "Test 10.%d [%s]: Encoding %d nullable list(s) following a single non-nullable list",
+                            nullableListCount, encodingMethodName, nullableListCount);
+
+            Instructions test;
+
+            // Add the single non-nullable list
             test.paths.push_back(ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id));
-            test.data.push_back(ListData::kNull);
+            test.data.push_back(ListData::kList);
+
+            // Add the nullable lists
+            for (int i = 0; i < nullableListCount; i++)
+            {
+                test.paths.push_back(ConcreteAttributePath(kTestEndpointId, AccessControl::Id, AccessControl::Attributes::Acl::Id));
+                test.data.push_back(ListData::kNull);
+            }
+
+            test.expectedStatus = { true };
+            RunTest_NonEmptyReplaceAll(test, encodingMethod);
         }
-
-        test.expectedStatus = { true };
-        RunTest_NonEmptyReplaceAll(test);
     }
-
     EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
 
     emberAfClearDynamicEndpoint(0);