Refactors native mock classes to be more like the Null device.

- Plumbs all descriptors through so that the objects act much more like
  a real implementation.
- Lots of changes to DestroyObjectTests.cpp to update usages and test
  through the APIs when applicable.
- Put in defaults for the mock device and make it as easy to use as
  possible.

Change-Id: I85b243a18ec1872aff0172549aec0f599967ea0e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/119821
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index c2819b8..5a2e3a4 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -456,10 +456,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-BindGroupBase::BindGroupBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) {
-    GetObjectTrackingList()->Track(this);
-}
-
 BindGroupBase::~BindGroupBase() = default;
 
 void BindGroupBase::DestroyImpl() {
diff --git a/src/dawn/native/BindGroup.h b/src/dawn/native/BindGroup.h
index 34c122b..01df670 100644
--- a/src/dawn/native/BindGroup.h
+++ b/src/dawn/native/BindGroup.h
@@ -76,8 +76,6 @@
         static_assert(std::is_base_of<BindGroupBase, Derived>::value);
     }
 
-    // Constructor used only for mocking and testing.
-    explicit BindGroupBase(DeviceBase* device);
     void DestroyImpl() override;
 
     ~BindGroupBase() override;
diff --git a/src/dawn/native/BindGroupLayout.cpp b/src/dawn/native/BindGroupLayout.cpp
index abc1e4c..125faa2 100644
--- a/src/dawn/native/BindGroupLayout.cpp
+++ b/src/dawn/native/BindGroupLayout.cpp
@@ -493,11 +493,6 @@
 BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag) {}
 
-BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device)
-    : ApiObjectBase(device, kLabelNotImplemented) {
-    GetObjectTrackingList()->Track(this);
-}
-
 BindGroupLayoutBase::~BindGroupLayoutBase() = default;
 
 void BindGroupLayoutBase::DestroyImpl() {
diff --git a/src/dawn/native/BindGroupLayout.h b/src/dawn/native/BindGroupLayout.h
index a218877..3b435cd 100644
--- a/src/dawn/native/BindGroupLayout.h
+++ b/src/dawn/native/BindGroupLayout.h
@@ -135,8 +135,6 @@
     std::string EntriesToString() const;
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit BindGroupLayoutBase(DeviceBase* device);
     void DestroyImpl() override;
 
     template <typename BindGroup>
diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index b8f8f0a..9f99a71 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -222,11 +222,6 @@
     }
 }
 
-BufferBase::BufferBase(DeviceBase* device, BufferState state)
-    : ApiObjectBase(device, kLabelNotImplemented), mState(state) {
-    GetObjectTrackingList()->Track(this);
-}
-
 BufferBase::~BufferBase() {
     ASSERT(mState == BufferState::Unmapped || mState == BufferState::Destroyed);
 }
diff --git a/src/dawn/native/Buffer.h b/src/dawn/native/Buffer.h
index d152d89..8e86055 100644
--- a/src/dawn/native/Buffer.h
+++ b/src/dawn/native/Buffer.h
@@ -96,8 +96,6 @@
   protected:
     BufferBase(DeviceBase* device, const BufferDescriptor* descriptor);
     BufferBase(DeviceBase* device, const BufferDescriptor* descriptor, ObjectBase::ErrorTag tag);
-    // Constructor used only for mocking and testing.
-    BufferBase(DeviceBase* device, BufferState state);
 
     void DestroyImpl() override;
 
diff --git a/src/dawn/native/CachedObject.cpp b/src/dawn/native/CachedObject.cpp
index 249b0d6..bf2be11 100644
--- a/src/dawn/native/CachedObject.cpp
+++ b/src/dawn/native/CachedObject.cpp
@@ -37,7 +37,7 @@
 }
 
 void CachedObject::SetContentHash(size_t contentHash) {
-    ASSERT(!mIsContentHashInitialized);
+    ASSERT(!mIsContentHashInitialized || contentHash == mContentHash);
     mContentHash = contentHash;
     mIsContentHashInitialized = true;
 }
diff --git a/src/dawn/native/CommandBuffer.cpp b/src/dawn/native/CommandBuffer.cpp
index 4a54530..061d1c1 100644
--- a/src/dawn/native/CommandBuffer.cpp
+++ b/src/dawn/native/CommandBuffer.cpp
@@ -33,11 +33,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-CommandBufferBase::CommandBufferBase(DeviceBase* device)
-    : ApiObjectBase(device, kLabelNotImplemented) {
-    GetObjectTrackingList()->Track(this);
-}
-
 CommandBufferBase::CommandBufferBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag) {}
 
diff --git a/src/dawn/native/CommandBuffer.h b/src/dawn/native/CommandBuffer.h
index 19d9f68..62b7c8b 100644
--- a/src/dawn/native/CommandBuffer.h
+++ b/src/dawn/native/CommandBuffer.h
@@ -45,8 +45,6 @@
     CommandIterator* GetCommandIteratorForTesting();
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit CommandBufferBase(DeviceBase* device);
     void DestroyImpl() override;
 
     CommandIterator mCommands;
diff --git a/src/dawn/native/ComputePipeline.cpp b/src/dawn/native/ComputePipeline.cpp
index a4b669b..93d2804 100644
--- a/src/dawn/native/ComputePipeline.cpp
+++ b/src/dawn/native/ComputePipeline.cpp
@@ -56,10 +56,6 @@
     StreamIn(&mCacheKey, CacheKey::Type::ComputePipeline, device->GetCacheKey());
 }
 
-ComputePipelineBase::ComputePipelineBase(DeviceBase* device) : PipelineBase(device) {
-    GetObjectTrackingList()->Track(this);
-}
-
 ComputePipelineBase::ComputePipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : PipelineBase(device, tag) {}
 
diff --git a/src/dawn/native/ComputePipeline.h b/src/dawn/native/ComputePipeline.h
index 36bb34f..f134579 100644
--- a/src/dawn/native/ComputePipeline.h
+++ b/src/dawn/native/ComputePipeline.h
@@ -42,8 +42,6 @@
     };
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit ComputePipelineBase(DeviceBase* device);
     void DestroyImpl() override;
 
   private:
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index b042919..d7132d6 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -125,6 +125,7 @@
     std::string mMessage;
     void* mUserdata;
 };
+}  // anonymous namespace
 
 ResultOrError<Ref<PipelineLayoutBase>> ValidateLayoutAndGetComputePipelineDescriptorWithDefaults(
     DeviceBase* device,
@@ -167,8 +168,6 @@
     return layoutRef;
 }
 
-}  // anonymous namespace
-
 // DeviceBase
 
 DeviceBase::DeviceBase(AdapterBase* adapter,
@@ -210,7 +209,7 @@
 }
 
 DeviceBase::DeviceBase() : mState(State::Alive), mToggles(ToggleStage::Device) {
-    mCaches = std::make_unique<DeviceBase::Caches>();
+    mFormatTable = BuildFormatTable(this);
 }
 
 DeviceBase::~DeviceBase() {
diff --git a/src/dawn/native/Device.h b/src/dawn/native/Device.h
index b97d2d9..ddb0e20 100644
--- a/src/dawn/native/Device.h
+++ b/src/dawn/native/Device.h
@@ -139,7 +139,7 @@
     MaybeError ValidateObject(const ApiObjectBase* object) const;
 
     AdapterBase* GetAdapter() const;
-    dawn::platform::Platform* GetPlatform() const;
+    virtual dawn::platform::Platform* GetPlatform() const;
 
     // Returns the Format corresponding to the wgpu::TextureFormat or an error if the format
     // isn't a valid wgpu::TextureFormat or isn't supported by this device.
@@ -582,6 +582,16 @@
     CacheKey mDeviceCacheKey;
 };
 
+ResultOrError<Ref<PipelineLayoutBase>> ValidateLayoutAndGetComputePipelineDescriptorWithDefaults(
+    DeviceBase* device,
+    const ComputePipelineDescriptor& descriptor,
+    ComputePipelineDescriptor* outDescriptor);
+
+ResultOrError<Ref<PipelineLayoutBase>> ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
+    DeviceBase* device,
+    const RenderPipelineDescriptor& descriptor,
+    RenderPipelineDescriptor* outDescriptor);
+
 class IgnoreLazyClearCountScope : public NonMovable {
   public:
     explicit IgnoreLazyClearCountScope(DeviceBase* device);
diff --git a/src/dawn/native/ExternalTexture.cpp b/src/dawn/native/ExternalTexture.cpp
index b95c90f..0e64216 100644
--- a/src/dawn/native/ExternalTexture.cpp
+++ b/src/dawn/native/ExternalTexture.cpp
@@ -137,11 +137,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-ExternalTextureBase::ExternalTextureBase(DeviceBase* device)
-    : ApiObjectBase(device, kLabelNotImplemented), mState(ExternalTextureState::Alive) {
-    GetObjectTrackingList()->Track(this);
-}
-
 // Error external texture cannot be used in bind group.
 ExternalTextureBase::ExternalTextureBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag), mState(ExternalTextureState::Destroyed) {}
diff --git a/src/dawn/native/ExternalTexture.h b/src/dawn/native/ExternalTexture.h
index 1e637b3..6cdeb13 100644
--- a/src/dawn/native/ExternalTexture.h
+++ b/src/dawn/native/ExternalTexture.h
@@ -60,18 +60,16 @@
     void APIDestroy();
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit ExternalTextureBase(DeviceBase* device);
+    ExternalTextureBase(DeviceBase* device, const ExternalTextureDescriptor* descriptor);
     void DestroyImpl() override;
 
+    MaybeError Initialize(DeviceBase* device, const ExternalTextureDescriptor* descriptor);
+
     ~ExternalTextureBase() override;
 
   private:
-    ExternalTextureBase(DeviceBase* device, const ExternalTextureDescriptor* descriptor);
-
     enum class ExternalTextureState { Alive, Destroyed };
     ExternalTextureBase(DeviceBase* device, ObjectBase::ErrorTag tag);
-    MaybeError Initialize(DeviceBase* device, const ExternalTextureDescriptor* descriptor);
 
     Ref<TextureBase> mPlaceholderTexture;
     Ref<BufferBase> mParamsBuffer;
diff --git a/src/dawn/native/Pipeline.cpp b/src/dawn/native/Pipeline.cpp
index 8738999..854d6a8 100644
--- a/src/dawn/native/Pipeline.cpp
+++ b/src/dawn/native/Pipeline.cpp
@@ -197,8 +197,6 @@
     }
 }
 
-PipelineBase::PipelineBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) {}
-
 PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag) {}
 
diff --git a/src/dawn/native/Pipeline.h b/src/dawn/native/Pipeline.h
index 212ebb4..ac879e4 100644
--- a/src/dawn/native/Pipeline.h
+++ b/src/dawn/native/Pipeline.h
@@ -83,9 +83,6 @@
                  std::vector<StageAndDescriptor> stages);
     PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag);
 
-    // Constructor used only for mocking and testing.
-    explicit PipelineBase(DeviceBase* device);
-
   private:
     MaybeError ValidateGetBindGroupLayout(BindGroupIndex group);
 
diff --git a/src/dawn/native/PipelineLayout.cpp b/src/dawn/native/PipelineLayout.cpp
index 887a519..39372be 100644
--- a/src/dawn/native/PipelineLayout.cpp
+++ b/src/dawn/native/PipelineLayout.cpp
@@ -74,11 +74,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device)
-    : ApiObjectBase(device, kLabelNotImplemented) {
-    GetObjectTrackingList()->Track(this);
-}
-
 PipelineLayoutBase::PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag) {}
 
diff --git a/src/dawn/native/PipelineLayout.h b/src/dawn/native/PipelineLayout.h
index c2536c2..11e9982 100644
--- a/src/dawn/native/PipelineLayout.h
+++ b/src/dawn/native/PipelineLayout.h
@@ -84,8 +84,6 @@
     };
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit PipelineLayoutBase(DeviceBase* device);
     PipelineLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag);
     void DestroyImpl() override;
 
diff --git a/src/dawn/native/QuerySet.cpp b/src/dawn/native/QuerySet.cpp
index 585e185..327f304 100644
--- a/src/dawn/native/QuerySet.cpp
+++ b/src/dawn/native/QuerySet.cpp
@@ -111,10 +111,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-QuerySetBase::QuerySetBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) {
-    GetObjectTrackingList()->Track(this);
-}
-
 QuerySetBase::QuerySetBase(DeviceBase* device,
                            const QuerySetDescriptor* descriptor,
                            ObjectBase::ErrorTag tag)
diff --git a/src/dawn/native/QuerySet.h b/src/dawn/native/QuerySet.h
index d0cfd89..a24f505 100644
--- a/src/dawn/native/QuerySet.h
+++ b/src/dawn/native/QuerySet.h
@@ -52,8 +52,6 @@
                  const QuerySetDescriptor* descriptor,
                  ObjectBase::ErrorTag tag);
 
-    // Constructor used only for mocking and testing.
-    explicit QuerySetBase(DeviceBase* device);
     void DestroyImpl() override;
 
     ~QuerySetBase() override;
diff --git a/src/dawn/native/RenderPipeline.cpp b/src/dawn/native/RenderPipeline.cpp
index da32d2d..ef234d2 100644
--- a/src/dawn/native/RenderPipeline.cpp
+++ b/src/dawn/native/RenderPipeline.cpp
@@ -672,10 +672,6 @@
     StreamIn(&mCacheKey, CacheKey::Type::RenderPipeline, device->GetCacheKey());
 }
 
-RenderPipelineBase::RenderPipelineBase(DeviceBase* device) : PipelineBase(device) {
-    GetObjectTrackingList()->Track(this);
-}
-
 RenderPipelineBase::RenderPipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : PipelineBase(device, tag) {}
 
diff --git a/src/dawn/native/RenderPipeline.h b/src/dawn/native/RenderPipeline.h
index a1eaee6..2f37473 100644
--- a/src/dawn/native/RenderPipeline.h
+++ b/src/dawn/native/RenderPipeline.h
@@ -113,8 +113,6 @@
     };
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit RenderPipelineBase(DeviceBase* device);
     void DestroyImpl() override;
 
   private:
diff --git a/src/dawn/native/Sampler.cpp b/src/dawn/native/Sampler.cpp
index 2205a3b..8f1b7b9 100644
--- a/src/dawn/native/Sampler.cpp
+++ b/src/dawn/native/Sampler.cpp
@@ -89,10 +89,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-SamplerBase::SamplerBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) {
-    GetObjectTrackingList()->Track(this);
-}
-
 SamplerBase::SamplerBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag) {}
 
diff --git a/src/dawn/native/Sampler.h b/src/dawn/native/Sampler.h
index eac3446..1384327 100644
--- a/src/dawn/native/Sampler.h
+++ b/src/dawn/native/Sampler.h
@@ -53,8 +53,6 @@
     uint16_t GetMaxAnisotropy() const { return mMaxAnisotropy; }
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit SamplerBase(DeviceBase* device);
     void DestroyImpl() override;
 
   private:
diff --git a/src/dawn/native/ShaderModule.cpp b/src/dawn/native/ShaderModule.cpp
index 8385fd8..48ad17f 100644
--- a/src/dawn/native/ShaderModule.cpp
+++ b/src/dawn/native/ShaderModule.cpp
@@ -1111,11 +1111,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-ShaderModuleBase::ShaderModuleBase(DeviceBase* device)
-    : ApiObjectBase(device, kLabelNotImplemented) {
-    GetObjectTrackingList()->Track(this);
-}
-
 ShaderModuleBase::ShaderModuleBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag), mType(Type::Undefined) {}
 
diff --git a/src/dawn/native/ShaderModule.h b/src/dawn/native/ShaderModule.h
index 5612d15..fd604a8 100644
--- a/src/dawn/native/ShaderModule.h
+++ b/src/dawn/native/ShaderModule.h
@@ -286,8 +286,6 @@
     OwnedCompilationMessages* GetCompilationMessages() const;
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit ShaderModuleBase(DeviceBase* device);
     void DestroyImpl() override;
 
     MaybeError InitializeBase(ShaderModuleParseResult* parseResult,
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index 3fe9e58..5c13abf 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -581,11 +581,6 @@
 
 static constexpr Format kUnusedFormat;
 
-TextureBase::TextureBase(DeviceBase* device, TextureState state)
-    : ApiObjectBase(device, kLabelNotImplemented), mFormat(kUnusedFormat), mState(state) {
-    GetObjectTrackingList()->Track(this);
-}
-
 TextureBase::TextureBase(DeviceBase* device,
                          const TextureDescriptor* descriptor,
                          ObjectBase::ErrorTag tag)
@@ -854,13 +849,6 @@
     GetObjectTrackingList()->Track(this);
 }
 
-TextureViewBase::TextureViewBase(TextureBase* texture)
-    : ApiObjectBase(texture->GetDevice(), kLabelNotImplemented),
-      mTexture(texture),
-      mFormat(kUnusedFormat) {
-    GetObjectTrackingList()->Track(this);
-}
-
 TextureViewBase::TextureViewBase(DeviceBase* device, ObjectBase::ErrorTag tag)
     : ApiObjectBase(device, tag), mFormat(kUnusedFormat) {}
 
diff --git a/src/dawn/native/Texture.h b/src/dawn/native/Texture.h
index 91372af..b6c2143 100644
--- a/src/dawn/native/Texture.h
+++ b/src/dawn/native/Texture.h
@@ -108,8 +108,6 @@
 
   protected:
     TextureBase(DeviceBase* device, const TextureDescriptor* descriptor, TextureState state);
-    // Constructor used only for mocking and testing.
-    TextureBase(DeviceBase* device, TextureState state);
     ~TextureBase() override;
 
     void DestroyImpl() override;
@@ -159,8 +157,6 @@
     const SubresourceRange& GetSubresourceRange() const;
 
   protected:
-    // Constructor used only for mocking and testing.
-    explicit TextureViewBase(TextureBase* texture);
     void DestroyImpl() override;
 
   private:
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 0b24f0b..fba9cf9 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -191,6 +191,7 @@
 
   deps = [
     ":gmock_and_gtest",
+    "${dawn_root}/src/dawn:proc",
     "${dawn_root}/src/dawn/native:sources",
     "${dawn_root}/src/dawn/native:static",
     "${dawn_root}/src/dawn/utils",
@@ -210,6 +211,9 @@
     "unittests/native/mocks/CommandBufferMock.h",
     "unittests/native/mocks/ComputePipelineMock.cpp",
     "unittests/native/mocks/ComputePipelineMock.h",
+    "unittests/native/mocks/DawnMockTest.cpp",
+    "unittests/native/mocks/DawnMockTest.h",
+    "unittests/native/mocks/DeviceMock.cpp",
     "unittests/native/mocks/DeviceMock.h",
     "unittests/native/mocks/ExternalTextureMock.cpp",
     "unittests/native/mocks/ExternalTextureMock.h",
@@ -217,6 +221,8 @@
     "unittests/native/mocks/PipelineLayoutMock.h",
     "unittests/native/mocks/QuerySetMock.cpp",
     "unittests/native/mocks/QuerySetMock.h",
+    "unittests/native/mocks/QueueMock.cpp",
+    "unittests/native/mocks/QueueMock.h",
     "unittests/native/mocks/RenderPipelineMock.cpp",
     "unittests/native/mocks/RenderPipelineMock.h",
     "unittests/native/mocks/SamplerMock.cpp",
diff --git a/src/dawn/tests/unittests/native/CreatePipelineAsyncTaskTests.cpp b/src/dawn/tests/unittests/native/CreatePipelineAsyncTaskTests.cpp
index dc03f0a..6131777 100644
--- a/src/dawn/tests/unittests/native/CreatePipelineAsyncTaskTests.cpp
+++ b/src/dawn/tests/unittests/native/CreatePipelineAsyncTaskTests.cpp
@@ -12,27 +12,45 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "dawn/tests/DawnNativeTest.h"
+#include <gtest/gtest.h>
 
 #include "dawn/native/CreatePipelineAsyncTask.h"
+#include "dawn/utils/WGPUHelpers.h"
 #include "mocks/ComputePipelineMock.h"
+#include "mocks/DawnMockTest.h"
 #include "mocks/RenderPipelineMock.h"
 
-class CreatePipelineAsyncTaskTests : public DawnNativeTest {};
+namespace dawn::native {
+namespace {
+
+using ::testing::Test;
+
+static constexpr std::string_view kComputeShader = R"(
+        @compute @workgroup_size(1) fn main() {}
+    )";
+
+static constexpr std::string_view kVertexShader = R"(
+        @vertex fn main() -> @builtin(position) vec4f {
+            return vec4f(0.0, 0.0, 0.0, 1.0);
+        }
+    )";
+
+class CreatePipelineAsyncTaskTests : public DawnMockTest {};
 
 // A regression test for a null pointer issue in CreateRenderPipelineAsyncTask::Run().
 // See crbug.com/dawn/1310 for more details.
 TEST_F(CreatePipelineAsyncTaskTests, InitializationValidationErrorInCreateRenderPipelineAsync) {
-    dawn::native::DeviceBase* deviceBase =
-        reinterpret_cast<dawn::native::DeviceBase*>(device.Get());
-    Ref<dawn::native::RenderPipelineMock> renderPipelineMock =
-        AcquireRef(new dawn::native::RenderPipelineMock(deviceBase));
+    wgpu::RenderPipelineDescriptor desc = {};
+    desc.vertex.module = utils::CreateShaderModule(device, kVertexShader.data());
+    desc.vertex.entryPoint = "main";
+    Ref<RenderPipelineMock> renderPipelineMock =
+        RenderPipelineMock::Create(mDeviceMock, FromCppAPI(&desc));
 
     ON_CALL(*renderPipelineMock.Get(), Initialize)
         .WillByDefault(testing::Return(testing::ByMove(
-            DAWN_MAKE_ERROR(dawn::native::InternalErrorType::Validation, "Initialization Error"))));
+            DAWN_MAKE_ERROR(InternalErrorType::Validation, "Initialization Error"))));
 
-    dawn::native::CreateRenderPipelineAsyncTask asyncTask(
+    CreateRenderPipelineAsyncTask asyncTask(
         renderPipelineMock,
         [](WGPUCreatePipelineAsyncStatus status, WGPURenderPipeline returnPipeline,
            const char* message, void* userdata) {
@@ -50,10 +68,11 @@
 // Test that Internal error are converted to the InternalError status in async pipeline creation
 // callbacks.
 TEST_F(CreatePipelineAsyncTaskTests, InitializationInternalErrorInCreateRenderPipelineAsync) {
-    dawn::native::DeviceBase* deviceBase =
-        reinterpret_cast<dawn::native::DeviceBase*>(device.Get());
-    Ref<dawn::native::RenderPipelineMock> renderPipelineMock =
-        AcquireRef(new dawn::native::RenderPipelineMock(deviceBase));
+    wgpu::RenderPipelineDescriptor desc = {};
+    desc.vertex.module = utils::CreateShaderModule(device, kVertexShader.data());
+    desc.vertex.entryPoint = "main";
+    Ref<RenderPipelineMock> renderPipelineMock =
+        RenderPipelineMock::Create(mDeviceMock, FromCppAPI(&desc));
 
     ON_CALL(*renderPipelineMock.Get(), Initialize)
         .WillByDefault(testing::Return(testing::ByMove(
@@ -77,16 +96,17 @@
 // A regression test for a null pointer issue in CreateComputePipelineAsyncTask::Run().
 // See crbug.com/dawn/1310 for more details.
 TEST_F(CreatePipelineAsyncTaskTests, InitializationValidationErrorInCreateComputePipelineAsync) {
-    dawn::native::DeviceBase* deviceBase =
-        reinterpret_cast<dawn::native::DeviceBase*>(device.Get());
-    Ref<dawn::native::ComputePipelineMock> computePipelineMock =
-        AcquireRef(new dawn::native::ComputePipelineMock(deviceBase));
+    wgpu::ComputePipelineDescriptor desc = {};
+    desc.compute.module = utils::CreateShaderModule(device, kComputeShader.data());
+    desc.compute.entryPoint = "main";
+    Ref<ComputePipelineMock> computePipelineMock =
+        ComputePipelineMock::Create(mDeviceMock, FromCppAPI(&desc));
 
     ON_CALL(*computePipelineMock.Get(), Initialize)
         .WillByDefault(testing::Return(testing::ByMove(
-            DAWN_MAKE_ERROR(dawn::native::InternalErrorType::Validation, "Initialization Error"))));
+            DAWN_MAKE_ERROR(InternalErrorType::Validation, "Initialization Error"))));
 
-    dawn::native::CreateComputePipelineAsyncTask asyncTask(
+    CreateComputePipelineAsyncTask asyncTask(
         computePipelineMock,
         [](WGPUCreatePipelineAsyncStatus status, WGPUComputePipeline returnPipeline,
            const char* message, void* userdata) {
@@ -104,10 +124,11 @@
 // Test that Internal error are converted to the InternalError status in async pipeline creation
 // callbacks.
 TEST_F(CreatePipelineAsyncTaskTests, InitializationInternalErrorInCreateComputePipelineAsync) {
-    dawn::native::DeviceBase* deviceBase =
-        reinterpret_cast<dawn::native::DeviceBase*>(device.Get());
-    Ref<dawn::native::ComputePipelineMock> computePipelineMock =
-        AcquireRef(new dawn::native::ComputePipelineMock(deviceBase));
+    wgpu::ComputePipelineDescriptor desc = {};
+    desc.compute.module = utils::CreateShaderModule(device, kComputeShader.data());
+    desc.compute.entryPoint = "main";
+    Ref<ComputePipelineMock> computePipelineMock =
+        ComputePipelineMock::Create(mDeviceMock, FromCppAPI(&desc));
 
     ON_CALL(*computePipelineMock.Get(), Initialize)
         .WillByDefault(testing::Return(testing::ByMove(
@@ -127,3 +148,6 @@
 
     EXPECT_CALL(*computePipelineMock.Get(), DestroyImpl).Times(1);
 }
+
+}  // namespace
+}  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/DestroyObjectTests.cpp b/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
index 4cc2c3b..8151f79 100644
--- a/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
+++ b/src/dawn/tests/unittests/native/DestroyObjectTests.cpp
@@ -14,15 +14,22 @@
 
 #include <gtest/gtest.h>
 
+#include <utility>
+#include <vector>
+
 #include "dawn/native/Toggles.h"
+#include "dawn/native/utils/WGPUHelpers.h"
 #include "dawn/tests/DawnNativeTest.h"
+#include "dawn/tests/MockCallback.h"
 #include "dawn/utils/ComboRenderPipelineDescriptor.h"
 #include "dawn/utils/WGPUHelpers.h"
+#include "dawn/webgpu_cpp.h"
 #include "mocks/BindGroupLayoutMock.h"
 #include "mocks/BindGroupMock.h"
 #include "mocks/BufferMock.h"
 #include "mocks/CommandBufferMock.h"
 #include "mocks/ComputePipelineMock.h"
+#include "mocks/DawnMockTest.h"
 #include "mocks/DeviceMock.h"
 #include "mocks/ExternalTextureMock.h"
 #include "mocks/PipelineLayoutMock.h"
@@ -39,721 +46,13 @@
 using ::testing::_;
 using ::testing::ByMove;
 using ::testing::InSequence;
+using ::testing::Mock;
+using testing::MockCallback;
+using ::testing::NiceMock;
 using ::testing::Return;
+using ::testing::StrictMock;
 using ::testing::Test;
 
-class DestroyObjectTests : public Test {
-  public:
-    DestroyObjectTests() : Test() {
-        // Skipping validation on descriptors as coverage for validation is already present.
-        mDevice.ForceSetToggleForTesting(Toggle::SkipValidation, true);
-    }
-
-    Ref<TextureMock> GetTexture() {
-        if (mTexture != nullptr) {
-            return mTexture;
-        }
-        mTexture = AcquireRef(new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal));
-        EXPECT_CALL(*mTexture.Get(), DestroyImpl).Times(1);
-        return mTexture;
-    }
-
-    Ref<PipelineLayoutMock> GetPipelineLayout() {
-        if (mPipelineLayout != nullptr) {
-            return mPipelineLayout;
-        }
-        mPipelineLayout = AcquireRef(new PipelineLayoutMock(&mDevice));
-        EXPECT_CALL(*mPipelineLayout.Get(), DestroyImpl).Times(1);
-        return mPipelineLayout;
-    }
-
-    Ref<ShaderModuleMock> GetVertexShaderModule() {
-        if (mVsModule != nullptr) {
-            return mVsModule;
-        }
-        DAWN_TRY_ASSIGN_WITH_CLEANUP(
-            mVsModule, ShaderModuleMock::Create(&mDevice, R"(
-            @vertex fn main() -> @builtin(position) vec4f {
-                return vec4f(0.0, 0.0, 0.0, 1.0);
-            })"),
-            { ASSERT(false); }, mVsModule);
-        EXPECT_CALL(*mVsModule.Get(), DestroyImpl).Times(1);
-        return mVsModule;
-    }
-
-    Ref<ShaderModuleMock> GetComputeShaderModule() {
-        if (mCsModule != nullptr) {
-            return mCsModule;
-        }
-        DAWN_TRY_ASSIGN_WITH_CLEANUP(
-            mCsModule, ShaderModuleMock::Create(&mDevice, R"(
-            @compute @workgroup_size(1) fn main() {
-            })"),
-            { ASSERT(false); }, mCsModule);
-        EXPECT_CALL(*mCsModule.Get(), DestroyImpl).Times(1);
-        return mCsModule;
-    }
-
-  protected:
-    DeviceMock mDevice;
-
-    // The following lazy-initialized objects are used to facilitate creation of dependent
-    // objects under test.
-    Ref<TextureMock> mTexture;
-    Ref<PipelineLayoutMock> mPipelineLayout;
-    Ref<ShaderModuleMock> mVsModule;
-    Ref<ShaderModuleMock> mCsModule;
-};
-
-TEST_F(DestroyObjectTests, BindGroupExplicit) {
-    BindGroupMock bindGroupMock(&mDevice);
-    EXPECT_CALL(bindGroupMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(bindGroupMock.IsAlive());
-    bindGroupMock.Destroy();
-    EXPECT_FALSE(bindGroupMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, BindGroupImplicit) {
-    BindGroupMock* bindGroupMock = new BindGroupMock(&mDevice);
-    EXPECT_CALL(*bindGroupMock, DestroyImpl).Times(1);
-    {
-        BindGroupDescriptor desc = {};
-        Ref<BindGroupBase> bindGroup;
-        EXPECT_CALL(mDevice, CreateBindGroupImpl)
-            .WillOnce(Return(ByMove(AcquireRef(bindGroupMock))));
-        DAWN_ASSERT_AND_ASSIGN(bindGroup, mDevice.CreateBindGroup(&desc));
-
-        EXPECT_TRUE(bindGroup->IsAlive());
-    }
-}
-
-TEST_F(DestroyObjectTests, BindGroupLayoutExplicit) {
-    BindGroupLayoutMock bindGroupLayoutMock(&mDevice);
-    EXPECT_CALL(bindGroupLayoutMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(bindGroupLayoutMock.IsAlive());
-    bindGroupLayoutMock.Destroy();
-    EXPECT_FALSE(bindGroupLayoutMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, BindGroupLayoutImplicit) {
-    BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&mDevice);
-    EXPECT_CALL(*bindGroupLayoutMock, DestroyImpl).Times(1);
-    {
-        BindGroupLayoutDescriptor desc = {};
-        Ref<BindGroupLayoutBase> bindGroupLayout;
-        EXPECT_CALL(mDevice, CreateBindGroupLayoutImpl)
-            .WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock))));
-        DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, mDevice.CreateBindGroupLayout(&desc));
-
-        EXPECT_TRUE(bindGroupLayout->IsAlive());
-        EXPECT_TRUE(bindGroupLayout->IsCachedReference());
-    }
-}
-
-TEST_F(DestroyObjectTests, BufferExplicit) {
-    {
-        BufferMock bufferMock(&mDevice, BufferBase::BufferState::Unmapped);
-        EXPECT_CALL(bufferMock, DestroyImpl).Times(1);
-
-        EXPECT_TRUE(bufferMock.IsAlive());
-        bufferMock.Destroy();
-        EXPECT_FALSE(bufferMock.IsAlive());
-    }
-    {
-        BufferMock bufferMock(&mDevice, BufferBase::BufferState::Mapped);
-        {
-            InSequence seq;
-            EXPECT_CALL(bufferMock, DestroyImpl).Times(1);
-            EXPECT_CALL(bufferMock, UnmapImpl).Times(1);
-        }
-
-        EXPECT_TRUE(bufferMock.IsAlive());
-        bufferMock.Destroy();
-        EXPECT_FALSE(bufferMock.IsAlive());
-    }
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, BufferImplicit) {
-    {
-        BufferMock* bufferMock = new BufferMock(&mDevice, BufferBase::BufferState::Unmapped);
-        EXPECT_CALL(*bufferMock, DestroyImpl).Times(1);
-        {
-            BufferDescriptor desc = {};
-            Ref<BufferBase> buffer;
-            EXPECT_CALL(mDevice, CreateBufferImpl).WillOnce(Return(ByMove(AcquireRef(bufferMock))));
-            DAWN_ASSERT_AND_ASSIGN(buffer, mDevice.CreateBuffer(&desc));
-
-            EXPECT_TRUE(buffer->IsAlive());
-        }
-    }
-    {
-        BufferMock* bufferMock = new BufferMock(&mDevice, BufferBase::BufferState::Mapped);
-        {
-            InSequence seq;
-            EXPECT_CALL(*bufferMock, DestroyImpl).Times(1);
-            EXPECT_CALL(*bufferMock, UnmapImpl).Times(1);
-        }
-        {
-            BufferDescriptor desc = {};
-            Ref<BufferBase> buffer;
-            EXPECT_CALL(mDevice, CreateBufferImpl).WillOnce(Return(ByMove(AcquireRef(bufferMock))));
-            DAWN_ASSERT_AND_ASSIGN(buffer, mDevice.CreateBuffer(&desc));
-
-            EXPECT_TRUE(buffer->IsAlive());
-        }
-    }
-}
-
-TEST_F(DestroyObjectTests, CommandBufferExplicit) {
-    CommandBufferMock commandBufferMock(&mDevice);
-    EXPECT_CALL(commandBufferMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(commandBufferMock.IsAlive());
-    commandBufferMock.Destroy();
-    EXPECT_FALSE(commandBufferMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, CommandBufferImplicit) {
-    CommandBufferMock* commandBufferMock = new CommandBufferMock(&mDevice);
-    EXPECT_CALL(*commandBufferMock, DestroyImpl).Times(1);
-    {
-        CommandBufferDescriptor desc = {};
-        Ref<CommandBufferBase> commandBuffer;
-        EXPECT_CALL(mDevice, CreateCommandBuffer)
-            .WillOnce(Return(ByMove(AcquireRef(commandBufferMock))));
-        DAWN_ASSERT_AND_ASSIGN(commandBuffer, mDevice.CreateCommandBuffer(nullptr, &desc));
-
-        EXPECT_TRUE(commandBuffer->IsAlive());
-    }
-}
-
-TEST_F(DestroyObjectTests, ComputePipelineExplicit) {
-    ComputePipelineMock computePipelineMock(&mDevice);
-    EXPECT_CALL(computePipelineMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(computePipelineMock.IsAlive());
-    computePipelineMock.Destroy();
-    EXPECT_FALSE(computePipelineMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, ComputePipelineImplicit) {
-    // ComputePipelines usually set their hash values at construction, but the mock does not, so
-    // we set it here.
-    constexpr size_t hash = 0x12345;
-    ComputePipelineMock* computePipelineMock = new ComputePipelineMock(&mDevice);
-    computePipelineMock->SetContentHash(hash);
-    ON_CALL(*computePipelineMock, ComputeContentHash).WillByDefault(Return(hash));
-
-    // Compute pipelines are initialized during their creation via the device.
-    EXPECT_CALL(*computePipelineMock, Initialize).Times(1);
-    EXPECT_CALL(*computePipelineMock, DestroyImpl).Times(1);
-
-    {
-        ComputePipelineDescriptor desc = {};
-        desc.layout = GetPipelineLayout().Get();
-        desc.compute.module = GetComputeShaderModule().Get();
-
-        Ref<ComputePipelineBase> computePipeline;
-        EXPECT_CALL(mDevice, CreateUninitializedComputePipelineImpl)
-            .WillOnce(Return(ByMove(AcquireRef(computePipelineMock))));
-        DAWN_ASSERT_AND_ASSIGN(computePipeline, mDevice.CreateComputePipeline(&desc));
-
-        EXPECT_TRUE(computePipeline->IsAlive());
-        EXPECT_TRUE(computePipeline->IsCachedReference());
-    }
-}
-
-TEST_F(DestroyObjectTests, ExternalTextureExplicit) {
-    ExternalTextureMock externalTextureMock(&mDevice);
-    EXPECT_CALL(externalTextureMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(externalTextureMock.IsAlive());
-    externalTextureMock.Destroy();
-    EXPECT_FALSE(externalTextureMock.IsAlive());
-}
-
-TEST_F(DestroyObjectTests, ExternalTextureImplicit) {
-    ExternalTextureMock* externalTextureMock = new ExternalTextureMock(&mDevice);
-    EXPECT_CALL(*externalTextureMock, DestroyImpl).Times(1);
-    {
-        ExternalTextureDescriptor desc = {};
-        Ref<ExternalTextureBase> externalTexture;
-        EXPECT_CALL(mDevice, CreateExternalTextureImpl)
-            .WillOnce(Return(ByMove(AcquireRef(externalTextureMock))));
-        DAWN_ASSERT_AND_ASSIGN(externalTexture, mDevice.CreateExternalTextureImpl(&desc));
-
-        EXPECT_TRUE(externalTexture->IsAlive());
-    }
-}
-
-TEST_F(DestroyObjectTests, PipelineLayoutExplicit) {
-    PipelineLayoutMock pipelineLayoutMock(&mDevice);
-    EXPECT_CALL(pipelineLayoutMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(pipelineLayoutMock.IsAlive());
-    pipelineLayoutMock.Destroy();
-    EXPECT_FALSE(pipelineLayoutMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, PipelineLayoutImplicit) {
-    PipelineLayoutMock* pipelineLayoutMock = new PipelineLayoutMock(&mDevice);
-    EXPECT_CALL(*pipelineLayoutMock, DestroyImpl).Times(1);
-    {
-        PipelineLayoutDescriptor desc = {};
-        Ref<PipelineLayoutBase> pipelineLayout;
-        EXPECT_CALL(mDevice, CreatePipelineLayoutImpl)
-            .WillOnce(Return(ByMove(AcquireRef(pipelineLayoutMock))));
-        DAWN_ASSERT_AND_ASSIGN(pipelineLayout, mDevice.CreatePipelineLayout(&desc));
-
-        EXPECT_TRUE(pipelineLayout->IsAlive());
-        EXPECT_TRUE(pipelineLayout->IsCachedReference());
-    }
-}
-
-TEST_F(DestroyObjectTests, QuerySetExplicit) {
-    QuerySetMock querySetMock(&mDevice);
-    EXPECT_CALL(querySetMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(querySetMock.IsAlive());
-    querySetMock.Destroy();
-    EXPECT_FALSE(querySetMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, QuerySetImplicit) {
-    QuerySetMock* querySetMock = new QuerySetMock(&mDevice);
-    EXPECT_CALL(*querySetMock, DestroyImpl).Times(1);
-    {
-        QuerySetDescriptor desc = {};
-        Ref<QuerySetBase> querySet;
-        EXPECT_CALL(mDevice, CreateQuerySetImpl).WillOnce(Return(ByMove(AcquireRef(querySetMock))));
-        DAWN_ASSERT_AND_ASSIGN(querySet, mDevice.CreateQuerySet(&desc));
-
-        EXPECT_TRUE(querySet->IsAlive());
-    }
-}
-
-TEST_F(DestroyObjectTests, RenderPipelineExplicit) {
-    RenderPipelineMock renderPipelineMock(&mDevice);
-    EXPECT_CALL(renderPipelineMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(renderPipelineMock.IsAlive());
-    renderPipelineMock.Destroy();
-    EXPECT_FALSE(renderPipelineMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, RenderPipelineImplicit) {
-    // RenderPipelines usually set their hash values at construction, but the mock does not, so
-    // we set it here.
-    constexpr size_t hash = 0x12345;
-    RenderPipelineMock* renderPipelineMock = new RenderPipelineMock(&mDevice);
-    renderPipelineMock->SetContentHash(hash);
-    ON_CALL(*renderPipelineMock, ComputeContentHash).WillByDefault(Return(hash));
-
-    // Render pipelines are initialized during their creation via the device.
-    EXPECT_CALL(*renderPipelineMock, Initialize).Times(1);
-    EXPECT_CALL(*renderPipelineMock, DestroyImpl).Times(1);
-
-    {
-        RenderPipelineDescriptor desc = {};
-        desc.layout = GetPipelineLayout().Get();
-        desc.vertex.module = GetVertexShaderModule().Get();
-
-        Ref<RenderPipelineBase> renderPipeline;
-        EXPECT_CALL(mDevice, CreateUninitializedRenderPipelineImpl)
-            .WillOnce(Return(ByMove(AcquireRef(renderPipelineMock))));
-        DAWN_ASSERT_AND_ASSIGN(renderPipeline, mDevice.CreateRenderPipeline(&desc));
-
-        EXPECT_TRUE(renderPipeline->IsAlive());
-        EXPECT_TRUE(renderPipeline->IsCachedReference());
-    }
-}
-
-TEST_F(DestroyObjectTests, SamplerExplicit) {
-    SamplerMock samplerMock(&mDevice);
-    EXPECT_CALL(samplerMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(samplerMock.IsAlive());
-    samplerMock.Destroy();
-    EXPECT_FALSE(samplerMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, SamplerImplicit) {
-    SamplerMock* samplerMock = new SamplerMock(&mDevice);
-    EXPECT_CALL(*samplerMock, DestroyImpl).Times(1);
-    {
-        SamplerDescriptor desc = {};
-        Ref<SamplerBase> sampler;
-        EXPECT_CALL(mDevice, CreateSamplerImpl).WillOnce(Return(ByMove(AcquireRef(samplerMock))));
-        DAWN_ASSERT_AND_ASSIGN(sampler, mDevice.CreateSampler(&desc));
-
-        EXPECT_TRUE(sampler->IsAlive());
-        EXPECT_TRUE(sampler->IsCachedReference());
-    }
-}
-
-TEST_F(DestroyObjectTests, ShaderModuleExplicit) {
-    ShaderModuleMock shaderModuleMock(&mDevice);
-    EXPECT_CALL(shaderModuleMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(shaderModuleMock.IsAlive());
-    shaderModuleMock.Destroy();
-    EXPECT_FALSE(shaderModuleMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, ShaderModuleImplicit) {
-    ShaderModuleMock* shaderModuleMock = new ShaderModuleMock(&mDevice);
-    EXPECT_CALL(*shaderModuleMock, DestroyImpl).Times(1);
-    {
-        ShaderModuleWGSLDescriptor wgslDesc;
-        wgslDesc.source = R"(
-                @compute @workgroup_size(1) fn main() {
-                }
-            )";
-        ShaderModuleDescriptor desc = {};
-        desc.nextInChain = &wgslDesc;
-        Ref<ShaderModuleBase> shaderModule;
-        EXPECT_CALL(mDevice, CreateShaderModuleImpl)
-            .WillOnce(Return(ByMove(AcquireRef(shaderModuleMock))));
-        DAWN_ASSERT_AND_ASSIGN(shaderModule, mDevice.CreateShaderModule(&desc));
-
-        EXPECT_TRUE(shaderModule->IsAlive());
-        EXPECT_TRUE(shaderModule->IsCachedReference());
-    }
-}
-
-TEST_F(DestroyObjectTests, SwapChainExplicit) {
-    SwapChainMock swapChainMock(&mDevice);
-    EXPECT_CALL(swapChainMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(swapChainMock.IsAlive());
-    swapChainMock.Destroy();
-    EXPECT_FALSE(swapChainMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, SwapChainImplicit) {
-    SwapChainMock* swapChainMock = new SwapChainMock(&mDevice);
-    EXPECT_CALL(*swapChainMock, DestroyImpl).Times(1);
-    {
-        SwapChainDescriptor desc = {};
-        Ref<SwapChainBase> swapChain;
-        EXPECT_CALL(mDevice, CreateSwapChainImpl(_))
-            .WillOnce(Return(ByMove(AcquireRef(swapChainMock))));
-        DAWN_ASSERT_AND_ASSIGN(swapChain, mDevice.CreateSwapChain(nullptr, &desc));
-
-        EXPECT_TRUE(swapChain->IsAlive());
-    }
-}
-
-TEST_F(DestroyObjectTests, TextureExplicit) {
-    {
-        TextureMock textureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
-        EXPECT_CALL(textureMock, DestroyImpl).Times(1);
-
-        EXPECT_TRUE(textureMock.IsAlive());
-        textureMock.Destroy();
-        EXPECT_FALSE(textureMock.IsAlive());
-    }
-    {
-        TextureMock textureMock(&mDevice, TextureBase::TextureState::OwnedExternal);
-        EXPECT_CALL(textureMock, DestroyImpl).Times(1);
-
-        EXPECT_TRUE(textureMock.IsAlive());
-        textureMock.Destroy();
-        EXPECT_FALSE(textureMock.IsAlive());
-    }
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, TextureImplicit) {
-    {
-        TextureMock* textureMock =
-            new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
-        EXPECT_CALL(*textureMock, DestroyImpl).Times(1);
-        {
-            TextureDescriptor desc = {};
-            Ref<TextureBase> texture;
-            EXPECT_CALL(mDevice, CreateTextureImpl)
-                .WillOnce(Return(ByMove(AcquireRef(textureMock))));
-            DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
-
-            EXPECT_TRUE(texture->IsAlive());
-        }
-    }
-    {
-        TextureMock* textureMock =
-            new TextureMock(&mDevice, TextureBase::TextureState::OwnedExternal);
-        EXPECT_CALL(*textureMock, DestroyImpl).Times(1);
-        {
-            TextureDescriptor desc = {};
-            Ref<TextureBase> texture;
-            EXPECT_CALL(mDevice, CreateTextureImpl)
-                .WillOnce(Return(ByMove(AcquireRef(textureMock))));
-            DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
-
-            EXPECT_TRUE(texture->IsAlive());
-        }
-    }
-}
-
-TEST_F(DestroyObjectTests, TextureViewExplicit) {
-    TextureViewMock textureViewMock(GetTexture().Get());
-    EXPECT_CALL(textureViewMock, DestroyImpl).Times(1);
-
-    EXPECT_TRUE(textureViewMock.IsAlive());
-    textureViewMock.Destroy();
-    EXPECT_FALSE(textureViewMock.IsAlive());
-}
-
-// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
-// will also complain if there is a memory leak.
-TEST_F(DestroyObjectTests, TextureViewImplicit) {
-    TextureViewMock* textureViewMock = new TextureViewMock(GetTexture().Get());
-    EXPECT_CALL(*textureViewMock, DestroyImpl).Times(1);
-    {
-        TextureViewDescriptor desc = {};
-        Ref<TextureViewBase> textureView;
-        EXPECT_CALL(mDevice, CreateTextureViewImpl)
-            .WillOnce(Return(ByMove(AcquireRef(textureViewMock))));
-        DAWN_ASSERT_AND_ASSIGN(textureView, mDevice.CreateTextureView(GetTexture().Get(), &desc));
-
-        EXPECT_TRUE(textureView->IsAlive());
-    }
-}
-
-// Destroying the objects on the mDevice should result in all created objects being destroyed in
-// order.
-TEST_F(DestroyObjectTests, DestroyObjects) {
-    BindGroupMock* bindGroupMock = new BindGroupMock(&mDevice);
-    BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&mDevice);
-    BufferMock* bufferMock = new BufferMock(&mDevice, BufferBase::BufferState::Unmapped);
-    CommandBufferMock* commandBufferMock = new CommandBufferMock(&mDevice);
-    ComputePipelineMock* computePipelineMock = new ComputePipelineMock(&mDevice);
-    ExternalTextureMock* externalTextureMock = new ExternalTextureMock(&mDevice);
-    PipelineLayoutMock* pipelineLayoutMock = new PipelineLayoutMock(&mDevice);
-    QuerySetMock* querySetMock = new QuerySetMock(&mDevice);
-    RenderPipelineMock* renderPipelineMock = new RenderPipelineMock(&mDevice);
-    SamplerMock* samplerMock = new SamplerMock(&mDevice);
-    ShaderModuleMock* shaderModuleMock = new ShaderModuleMock(&mDevice);
-    SwapChainMock* swapChainMock = new SwapChainMock(&mDevice);
-    TextureMock* textureMock = new TextureMock(&mDevice, TextureBase::TextureState::OwnedInternal);
-    TextureViewMock* textureViewMock = new TextureViewMock(GetTexture().Get());
-    {
-        InSequence seq;
-        EXPECT_CALL(*commandBufferMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*renderPipelineMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*computePipelineMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*pipelineLayoutMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*swapChainMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*bindGroupMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*bindGroupLayoutMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*shaderModuleMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*externalTextureMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*textureViewMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*textureMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*querySetMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*samplerMock, DestroyImpl).Times(1);
-        EXPECT_CALL(*bufferMock, DestroyImpl).Times(1);
-    }
-
-    Ref<BindGroupBase> bindGroup;
-    {
-        BindGroupDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateBindGroupImpl)
-            .WillOnce(Return(ByMove(AcquireRef(bindGroupMock))));
-        DAWN_ASSERT_AND_ASSIGN(bindGroup, mDevice.CreateBindGroup(&desc));
-        EXPECT_TRUE(bindGroup->IsAlive());
-    }
-
-    Ref<BindGroupLayoutBase> bindGroupLayout;
-    {
-        BindGroupLayoutDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateBindGroupLayoutImpl)
-            .WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock))));
-        DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, mDevice.CreateBindGroupLayout(&desc));
-        EXPECT_TRUE(bindGroupLayout->IsAlive());
-        EXPECT_TRUE(bindGroupLayout->IsCachedReference());
-    }
-
-    Ref<BufferBase> buffer;
-    {
-        BufferDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateBufferImpl).WillOnce(Return(ByMove(AcquireRef(bufferMock))));
-        DAWN_ASSERT_AND_ASSIGN(buffer, mDevice.CreateBuffer(&desc));
-        EXPECT_TRUE(buffer->IsAlive());
-    }
-
-    Ref<CommandBufferBase> commandBuffer;
-    {
-        CommandBufferDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateCommandBuffer)
-            .WillOnce(Return(ByMove(AcquireRef(commandBufferMock))));
-        DAWN_ASSERT_AND_ASSIGN(commandBuffer, mDevice.CreateCommandBuffer(nullptr, &desc));
-        EXPECT_TRUE(commandBuffer->IsAlive());
-    }
-
-    Ref<ComputePipelineBase> computePipeline;
-    {
-        // Compute pipelines usually set their hash values at construction, but the mock does
-        // not, so we set it here.
-        constexpr size_t hash = 0x12345;
-        computePipelineMock->SetContentHash(hash);
-        ON_CALL(*computePipelineMock, ComputeContentHash).WillByDefault(Return(hash));
-
-        // Compute pipelines are initialized during their creation via the device.
-        EXPECT_CALL(*computePipelineMock, Initialize).Times(1);
-
-        ComputePipelineDescriptor desc = {};
-        desc.layout = GetPipelineLayout().Get();
-        desc.compute.module = GetComputeShaderModule().Get();
-        EXPECT_CALL(mDevice, CreateUninitializedComputePipelineImpl)
-            .WillOnce(Return(ByMove(AcquireRef(computePipelineMock))));
-        DAWN_ASSERT_AND_ASSIGN(computePipeline, mDevice.CreateComputePipeline(&desc));
-        EXPECT_TRUE(computePipeline->IsAlive());
-        EXPECT_TRUE(computePipeline->IsCachedReference());
-    }
-
-    Ref<ExternalTextureBase> externalTexture;
-    {
-        ExternalTextureDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateExternalTextureImpl)
-            .WillOnce(Return(ByMove(AcquireRef(externalTextureMock))));
-        DAWN_ASSERT_AND_ASSIGN(externalTexture, mDevice.CreateExternalTextureImpl(&desc));
-        EXPECT_TRUE(externalTexture->IsAlive());
-    }
-
-    Ref<PipelineLayoutBase> pipelineLayout;
-    {
-        PipelineLayoutDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreatePipelineLayoutImpl)
-            .WillOnce(Return(ByMove(AcquireRef(pipelineLayoutMock))));
-        DAWN_ASSERT_AND_ASSIGN(pipelineLayout, mDevice.CreatePipelineLayout(&desc));
-        EXPECT_TRUE(pipelineLayout->IsAlive());
-        EXPECT_TRUE(pipelineLayout->IsCachedReference());
-    }
-
-    Ref<QuerySetBase> querySet;
-    {
-        QuerySetDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateQuerySetImpl).WillOnce(Return(ByMove(AcquireRef(querySetMock))));
-        DAWN_ASSERT_AND_ASSIGN(querySet, mDevice.CreateQuerySet(&desc));
-        EXPECT_TRUE(querySet->IsAlive());
-    }
-
-    Ref<RenderPipelineBase> renderPipeline;
-    {
-        // Render pipelines usually set their hash values at construction, but the mock does
-        // not, so we set it here.
-        constexpr size_t hash = 0x12345;
-        renderPipelineMock->SetContentHash(hash);
-        ON_CALL(*renderPipelineMock, ComputeContentHash).WillByDefault(Return(hash));
-
-        // Render pipelines are initialized during their creation via the device.
-        EXPECT_CALL(*renderPipelineMock, Initialize).Times(1);
-
-        RenderPipelineDescriptor desc = {};
-        desc.layout = GetPipelineLayout().Get();
-        desc.vertex.module = GetVertexShaderModule().Get();
-        EXPECT_CALL(mDevice, CreateUninitializedRenderPipelineImpl)
-            .WillOnce(Return(ByMove(AcquireRef(renderPipelineMock))));
-        DAWN_ASSERT_AND_ASSIGN(renderPipeline, mDevice.CreateRenderPipeline(&desc));
-        EXPECT_TRUE(renderPipeline->IsAlive());
-        EXPECT_TRUE(renderPipeline->IsCachedReference());
-    }
-
-    Ref<SamplerBase> sampler;
-    {
-        SamplerDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateSamplerImpl).WillOnce(Return(ByMove(AcquireRef(samplerMock))));
-        DAWN_ASSERT_AND_ASSIGN(sampler, mDevice.CreateSampler(&desc));
-        EXPECT_TRUE(sampler->IsAlive());
-        EXPECT_TRUE(sampler->IsCachedReference());
-    }
-
-    Ref<ShaderModuleBase> shaderModule;
-    {
-        ShaderModuleWGSLDescriptor wgslDesc;
-        wgslDesc.source = R"(
-                @compute @workgroup_size(1) fn main() {
-                }
-            )";
-        ShaderModuleDescriptor desc = {};
-        desc.nextInChain = &wgslDesc;
-
-        EXPECT_CALL(mDevice, CreateShaderModuleImpl)
-            .WillOnce(Return(ByMove(AcquireRef(shaderModuleMock))));
-        DAWN_ASSERT_AND_ASSIGN(shaderModule, mDevice.CreateShaderModule(&desc));
-        EXPECT_TRUE(shaderModule->IsAlive());
-        EXPECT_TRUE(shaderModule->IsCachedReference());
-    }
-
-    Ref<SwapChainBase> swapChain;
-    {
-        SwapChainDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateSwapChainImpl(_))
-            .WillOnce(Return(ByMove(AcquireRef(swapChainMock))));
-        DAWN_ASSERT_AND_ASSIGN(swapChain, mDevice.CreateSwapChain(nullptr, &desc));
-        EXPECT_TRUE(swapChain->IsAlive());
-    }
-
-    Ref<TextureBase> texture;
-    {
-        TextureDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateTextureImpl).WillOnce(Return(ByMove(AcquireRef(textureMock))));
-        DAWN_ASSERT_AND_ASSIGN(texture, mDevice.CreateTexture(&desc));
-        EXPECT_TRUE(texture->IsAlive());
-    }
-
-    Ref<TextureViewBase> textureView;
-    {
-        TextureViewDescriptor desc = {};
-        EXPECT_CALL(mDevice, CreateTextureViewImpl)
-            .WillOnce(Return(ByMove(AcquireRef(textureViewMock))));
-        DAWN_ASSERT_AND_ASSIGN(textureView, mDevice.CreateTextureView(GetTexture().Get(), &desc));
-        EXPECT_TRUE(textureView->IsAlive());
-    }
-
-    mDevice.DestroyObjects();
-    EXPECT_FALSE(bindGroup->IsAlive());
-    EXPECT_FALSE(bindGroupLayout->IsAlive());
-    EXPECT_FALSE(buffer->IsAlive());
-    EXPECT_FALSE(commandBuffer->IsAlive());
-    EXPECT_FALSE(computePipeline->IsAlive());
-    EXPECT_FALSE(externalTexture->IsAlive());
-    EXPECT_FALSE(pipelineLayout->IsAlive());
-    EXPECT_FALSE(querySet->IsAlive());
-    EXPECT_FALSE(renderPipeline->IsAlive());
-    EXPECT_FALSE(sampler->IsAlive());
-    EXPECT_FALSE(shaderModule->IsAlive());
-    EXPECT_FALSE(swapChain->IsAlive());
-    EXPECT_FALSE(texture->IsAlive());
-    EXPECT_FALSE(textureView->IsAlive());
-}
-
 static constexpr std::string_view kComputeShader = R"(
         @compute @workgroup_size(1) fn main() {}
     )";
@@ -768,6 +67,971 @@
         @fragment fn main() {}
     )";
 
+// Stores and scopes a raw mock object ptr expectation. This is particularly useful on objects that
+// are expected to be destroyed at the end of the scope. In most cases, when the validation in this
+// class's destructor is ran, the pointer is probably already freed.
+class ScopedRawPtrExpectation {
+  public:
+    explicit ScopedRawPtrExpectation(void* ptr) : mPtr(ptr) {}
+    ~ScopedRawPtrExpectation() { Mock::VerifyAndClearExpectations(mPtr); }
+
+  private:
+    void* mPtr = nullptr;
+};
+
+class DestroyObjectTests : public DawnMockTest {
+  public:
+    DestroyObjectTests() : DawnMockTest() {
+        // Skipping validation on descriptors as coverage for validation is already present.
+        mDeviceMock->ForceSetToggleForTesting(Toggle::SkipValidation, true);
+    }
+};
+
+TEST_F(DestroyObjectTests, BindGroupNativeExplicit) {
+    BindGroupDescriptor desc = {};
+    desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock();
+    desc.entryCount = 0;
+    desc.entries = nullptr;
+
+    Ref<BindGroupMock> bindGroupMock = AcquireRef(new BindGroupMock(mDeviceMock, &desc));
+    EXPECT_CALL(*bindGroupMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(bindGroupMock->IsAlive());
+    bindGroupMock->Destroy();
+    EXPECT_FALSE(bindGroupMock->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, BindGroupImplicit) {
+    BindGroupDescriptor desc = {};
+    desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock();
+    desc.entryCount = 0;
+    desc.entries = nullptr;
+
+    Ref<BindGroupMock> bindGroupMock = AcquireRef(new BindGroupMock(mDeviceMock, &desc));
+    EXPECT_CALL(*bindGroupMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(bindGroupMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateBindGroupImpl)
+            .WillOnce(Return(ByMove(std::move(bindGroupMock))));
+        wgpu::BindGroup bindGroup = device.CreateBindGroup(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(bindGroup.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, BindGroupLayoutNativeExplicit) {
+    // Use an non-empty bind group layout to avoid hitting the internal empty layout in the cache.
+    BindGroupLayoutDescriptor desc = {};
+    std::vector<BindGroupLayoutEntry> entries;
+    entries.push_back(utils::BindingLayoutEntryInitializationHelper(
+        0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform));
+    desc.entryCount = static_cast<uint32_t>(entries.size());
+    desc.entries = entries.data();
+
+    Ref<BindGroupLayoutMock> bindGroupLayoutMock =
+        AcquireRef(new BindGroupLayoutMock(mDeviceMock, &desc));
+    EXPECT_CALL(*bindGroupLayoutMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(bindGroupLayoutMock->IsAlive());
+    bindGroupLayoutMock->Destroy();
+    EXPECT_FALSE(bindGroupLayoutMock->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, BindGroupLayoutImplicit) {
+    // Use an non-empty bind group layout to avoid hitting the internal empty layout in the cache.
+    BindGroupLayoutDescriptor desc = {};
+    std::vector<BindGroupLayoutEntry> entries;
+    entries.push_back(utils::BindingLayoutEntryInitializationHelper(
+        0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform));
+    desc.entryCount = static_cast<uint32_t>(entries.size());
+    desc.entries = entries.data();
+
+    Ref<BindGroupLayoutMock> bindGroupLayoutMock =
+        AcquireRef(new BindGroupLayoutMock(mDeviceMock, &desc));
+    EXPECT_CALL(*bindGroupLayoutMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(bindGroupLayoutMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateBindGroupLayoutImpl)
+            .WillOnce(Return(ByMove(std::move(bindGroupLayoutMock))));
+        wgpu::BindGroupLayout bindGroupLayout = device.CreateBindGroupLayout(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(bindGroupLayout.Get())->IsAlive());
+        EXPECT_TRUE(FromAPI(bindGroupLayout.Get())->IsCachedReference());
+    }
+}
+
+TEST_F(DestroyObjectTests, BufferNativeExplicit) {
+    BufferDescriptor desc = {};
+    desc.size = 16;
+    desc.usage = wgpu::BufferUsage::Uniform;
+
+    Ref<BufferMock> bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc));
+    EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(bufferMock->IsAlive());
+    bufferMock->Destroy();
+    EXPECT_FALSE(bufferMock->IsAlive());
+}
+
+TEST_F(DestroyObjectTests, BufferApiExplicit) {
+    BufferDescriptor desc = {};
+    desc.size = 16;
+    desc.usage = wgpu::BufferUsage::Uniform;
+
+    Ref<BufferMock> bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc));
+    EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock))));
+    wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc));
+
+    EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive());
+    buffer.Destroy();
+    EXPECT_FALSE(FromAPI(buffer.Get())->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, BufferImplicit) {
+    BufferDescriptor desc = {};
+    desc.size = 16;
+    desc.usage = wgpu::BufferUsage::Uniform;
+
+    Ref<BufferMock> bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc));
+    EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(bufferMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock))));
+        wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, MappedBufferApiExplicit) {
+    BufferDescriptor desc = {};
+    desc.size = 16;
+    desc.usage = wgpu::BufferUsage::MapRead;
+
+    StrictMock<MockCallback<wgpu::BufferMapCallback>> cb;
+    EXPECT_CALL(cb, Call).Times(1);
+    Ref<BufferMock> bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc));
+    {
+        InSequence seq;
+        EXPECT_CALL(*bufferMock.Get(), MapAsyncImpl).WillOnce([]() -> MaybeError { return {}; });
+        EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*bufferMock.Get(), UnmapImpl).Times(1);
+    }
+    {
+        EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock))));
+        wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc));
+        buffer.MapAsync(wgpu::MapMode::Read, 0, 16, cb.Callback(), cb.MakeUserdata(this));
+        device.Tick();
+
+        EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive());
+        buffer.Destroy();
+        EXPECT_FALSE(FromAPI(buffer.Get())->IsAlive());
+    }
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, MappedBufferImplicit) {
+    BufferDescriptor desc = {};
+    desc.size = 16;
+    desc.usage = wgpu::BufferUsage::MapRead;
+
+    StrictMock<MockCallback<wgpu::BufferMapCallback>> cb;
+    EXPECT_CALL(cb, Call).Times(1);
+    Ref<BufferMock> bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc));
+    {
+        InSequence seq;
+        EXPECT_CALL(*bufferMock.Get(), MapAsyncImpl).WillOnce([]() -> MaybeError { return {}; });
+        EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*bufferMock.Get(), UnmapImpl).Times(1);
+    }
+    {
+        ScopedRawPtrExpectation scoped(bufferMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(ByMove(std::move(bufferMock))));
+        wgpu::Buffer buffer = device.CreateBuffer(ToCppAPI(&desc));
+        buffer.MapAsync(wgpu::MapMode::Read, 0, 16, cb.Callback(), cb.MakeUserdata(this));
+        device.Tick();
+
+        EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, CommandBufferNativeExplicit) {
+    CommandEncoderDescriptor commandEncoderDesc = {};
+    Ref<CommandEncoder> commandEncoder = CommandEncoder::Create(mDeviceMock, &commandEncoderDesc);
+
+    CommandBufferDescriptor commandBufferDesc = {};
+
+    Ref<CommandBufferMock> commandBufferMock =
+        AcquireRef(new CommandBufferMock(mDeviceMock, commandEncoder.Get(), &commandBufferDesc));
+    EXPECT_CALL(*commandBufferMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(commandBufferMock->IsAlive());
+    commandBufferMock->Destroy();
+    EXPECT_FALSE(commandBufferMock->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, CommandBufferImplicit) {
+    CommandEncoderDescriptor commandEncoderDesc = {};
+    wgpu::CommandEncoder commandEncoder =
+        device.CreateCommandEncoder(ToCppAPI(&commandEncoderDesc));
+
+    CommandBufferDescriptor commandBufferDesc = {};
+
+    Ref<CommandBufferMock> commandBufferMock = AcquireRef(
+        new CommandBufferMock(mDeviceMock, FromAPI(commandEncoder.Get()), &commandBufferDesc));
+    EXPECT_CALL(*commandBufferMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(commandBufferMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateCommandBuffer)
+            .WillOnce(Return(ByMove(std::move(commandBufferMock))));
+        wgpu::CommandBuffer commandBuffer = commandEncoder.Finish(ToCppAPI(&commandBufferDesc));
+
+        EXPECT_TRUE(FromAPI(commandBuffer.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, ComputePipelineNativeExplicit) {
+    Ref<ShaderModuleMock> csModuleMock =
+        ShaderModuleMock::Create(mDeviceMock, kComputeShader.data());
+    ComputePipelineDescriptor desc = {};
+    desc.compute.module = csModuleMock.Get();
+    desc.compute.entryPoint = "main";
+
+    Ref<ComputePipelineMock> computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*computePipelineMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(computePipelineMock->IsAlive());
+    computePipelineMock->Destroy();
+    EXPECT_FALSE(computePipelineMock->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, ComputePipelineImplicit) {
+    Ref<ShaderModuleMock> csModuleMock =
+        ShaderModuleMock::Create(mDeviceMock, kComputeShader.data());
+    ComputePipelineDescriptor desc = {};
+    desc.compute.module = csModuleMock.Get();
+    desc.compute.entryPoint = "main";
+
+    // Compute pipelines are initialized during their creation via the device.
+    Ref<ComputePipelineMock> computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*computePipelineMock.Get(), Initialize).Times(1);
+    EXPECT_CALL(*computePipelineMock.Get(), DestroyImpl).Times(1);
+
+    {
+        ScopedRawPtrExpectation scoped(computePipelineMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl)
+            .WillOnce(Return(ByMove(std::move(computePipelineMock))));
+        wgpu::ComputePipeline computePipeline = device.CreateComputePipeline(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(computePipeline.Get())->IsAlive());
+        EXPECT_TRUE(FromAPI(computePipeline.Get())->IsCachedReference());
+    }
+}
+
+TEST_F(DestroyObjectTests, ExternalTextureNativeExplicit) {
+    TextureDescriptor textureDesc = {};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    textureDesc.size.width = 1;
+    textureDesc.size.height = 1;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
+
+    TextureViewDescriptor textureViewDesc = {};
+    textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureViewMock> textureViewMock =
+        AcquireRef(new NiceMock<TextureViewMock>(textureMock.Get(), &textureViewDesc));
+
+    ExternalTextureDescriptor desc = {};
+    std::array<float, 12> placeholderConstantArray;
+    desc.yuvToRgbConversionMatrix = placeholderConstantArray.data();
+    desc.gamutConversionMatrix = placeholderConstantArray.data();
+    desc.srcTransferFunctionParameters = placeholderConstantArray.data();
+    desc.dstTransferFunctionParameters = placeholderConstantArray.data();
+    desc.visibleSize = {1, 1};
+    desc.plane0 = textureViewMock.Get();
+
+    Ref<ExternalTextureMock> externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(externalTextureMock->IsAlive());
+    externalTextureMock->Destroy();
+    EXPECT_FALSE(externalTextureMock->IsAlive());
+}
+
+TEST_F(DestroyObjectTests, ExternalTextureApiExplicit) {
+    TextureDescriptor textureDesc = {};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    textureDesc.size.width = 1;
+    textureDesc.size.height = 1;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
+
+    TextureViewDescriptor textureViewDesc = {};
+    textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureViewMock> textureViewMock =
+        AcquireRef(new NiceMock<TextureViewMock>(textureMock.Get(), &textureViewDesc));
+
+    ExternalTextureDescriptor desc = {};
+    std::array<float, 12> placeholderConstantArray;
+    desc.yuvToRgbConversionMatrix = placeholderConstantArray.data();
+    desc.gamutConversionMatrix = placeholderConstantArray.data();
+    desc.srcTransferFunctionParameters = placeholderConstantArray.data();
+    desc.dstTransferFunctionParameters = placeholderConstantArray.data();
+    desc.visibleSize = {1, 1};
+    desc.plane0 = textureViewMock.Get();
+
+    Ref<ExternalTextureMock> externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_CALL(*mDeviceMock, CreateExternalTextureImpl)
+        .WillOnce(Return(ByMove(std::move(externalTextureMock))));
+    wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(ToCppAPI(&desc));
+
+    EXPECT_TRUE(FromAPI(externalTexture.Get())->IsAlive());
+    externalTexture.Destroy();
+    EXPECT_FALSE(FromAPI(externalTexture.Get())->IsAlive());
+}
+
+TEST_F(DestroyObjectTests, ExternalTextureImplicit) {
+    TextureDescriptor textureDesc = {};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    textureDesc.size.width = 1;
+    textureDesc.size.height = 1;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
+
+    TextureViewDescriptor textureViewDesc = {};
+    textureViewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureViewMock> textureViewMock =
+        AcquireRef(new NiceMock<TextureViewMock>(textureMock.Get(), &textureViewDesc));
+
+    ExternalTextureDescriptor desc = {};
+    std::array<float, 12> placeholderConstantArray;
+    desc.yuvToRgbConversionMatrix = placeholderConstantArray.data();
+    desc.gamutConversionMatrix = placeholderConstantArray.data();
+    desc.srcTransferFunctionParameters = placeholderConstantArray.data();
+    desc.dstTransferFunctionParameters = placeholderConstantArray.data();
+    desc.visibleSize = {1, 1};
+    desc.plane0 = textureViewMock.Get();
+
+    Ref<ExternalTextureMock> externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(externalTextureMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateExternalTextureImpl)
+            .WillOnce(Return(ByMove(std::move(externalTextureMock))));
+        wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(externalTexture.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, PipelineLayoutNativeExplicit) {
+    PipelineLayoutDescriptor desc = {};
+    std::vector<BindGroupLayoutBase*> bindGroupLayouts;
+    bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock());
+    desc.bindGroupLayoutCount = static_cast<uint32_t>(bindGroupLayouts.size());
+    desc.bindGroupLayouts = bindGroupLayouts.data();
+
+    Ref<PipelineLayoutMock> pipelineLayoutMock =
+        AcquireRef(new PipelineLayoutMock(mDeviceMock, &desc));
+    EXPECT_CALL(*pipelineLayoutMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(pipelineLayoutMock->IsAlive());
+    pipelineLayoutMock->Destroy();
+    EXPECT_FALSE(pipelineLayoutMock->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, PipelineLayoutImplicit) {
+    PipelineLayoutDescriptor desc = {};
+    std::vector<BindGroupLayoutBase*> bindGroupLayouts;
+    bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock());
+    desc.bindGroupLayoutCount = static_cast<uint32_t>(bindGroupLayouts.size());
+    desc.bindGroupLayouts = bindGroupLayouts.data();
+
+    Ref<PipelineLayoutMock> pipelineLayoutMock =
+        AcquireRef(new PipelineLayoutMock(mDeviceMock, &desc));
+    EXPECT_CALL(*pipelineLayoutMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(pipelineLayoutMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreatePipelineLayoutImpl)
+            .WillOnce(Return(ByMove(std::move(pipelineLayoutMock))));
+        wgpu::PipelineLayout pipelineLayout = device.CreatePipelineLayout(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(pipelineLayout.Get())->IsAlive());
+        EXPECT_TRUE(FromAPI(pipelineLayout.Get())->IsCachedReference());
+    }
+}
+
+TEST_F(DestroyObjectTests, QuerySetNativeExplicit) {
+    QuerySetDescriptor desc = {};
+    desc.type = wgpu::QueryType::Occlusion;
+    desc.count = 1;
+
+    Ref<QuerySetMock> querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc));
+    EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(querySetMock->IsAlive());
+    querySetMock->Destroy();
+    EXPECT_FALSE(querySetMock->IsAlive());
+}
+
+TEST_F(DestroyObjectTests, QuerySetApiExplicit) {
+    QuerySetDescriptor desc = {};
+    desc.type = wgpu::QueryType::Occlusion;
+    desc.count = 1;
+
+    Ref<QuerySetMock> querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc));
+    EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_CALL(*mDeviceMock, CreateQuerySetImpl).WillOnce(Return(ByMove(std::move(querySetMock))));
+    wgpu::QuerySet querySet = device.CreateQuerySet(ToCppAPI(&desc));
+
+    EXPECT_TRUE(FromAPI(querySet.Get())->IsAlive());
+    querySet.Destroy();
+    EXPECT_FALSE(FromAPI(querySet.Get())->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, QuerySetImplicit) {
+    QuerySetDescriptor desc = {};
+    desc.type = wgpu::QueryType::Occlusion;
+    desc.count = 1;
+
+    Ref<QuerySetMock> querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc));
+    EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(querySetMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateQuerySetImpl)
+            .WillOnce(Return(ByMove(std::move(querySetMock))));
+        wgpu::QuerySet querySet = device.CreateQuerySet(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(querySet.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, RenderPipelineNativeExplicit) {
+    Ref<ShaderModuleMock> vsModuleMock =
+        ShaderModuleMock::Create(mDeviceMock, kVertexShader.data());
+    RenderPipelineDescriptor desc = {};
+    desc.vertex.module = vsModuleMock.Get();
+    desc.vertex.entryPoint = "main";
+
+    Ref<RenderPipelineMock> renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*renderPipelineMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(renderPipelineMock->IsAlive());
+    renderPipelineMock->Destroy();
+    EXPECT_FALSE(renderPipelineMock->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, RenderPipelineImplicit) {
+    Ref<ShaderModuleMock> vsModuleMock =
+        ShaderModuleMock::Create(mDeviceMock, kVertexShader.data());
+    RenderPipelineDescriptor desc = {};
+    desc.vertex.module = vsModuleMock.Get();
+    desc.vertex.entryPoint = "main";
+
+    // Render pipelines are initialized during their creation via the device.
+    Ref<RenderPipelineMock> renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*renderPipelineMock.Get(), Initialize).Times(1);
+    EXPECT_CALL(*renderPipelineMock.Get(), DestroyImpl).Times(1);
+
+    {
+        ScopedRawPtrExpectation scoped(renderPipelineMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl)
+            .WillOnce(Return(ByMove(std::move(renderPipelineMock))));
+        wgpu::RenderPipeline renderPipeline = device.CreateRenderPipeline(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(renderPipeline.Get())->IsAlive());
+        EXPECT_TRUE(FromAPI(renderPipeline.Get())->IsCachedReference());
+    }
+}
+
+TEST_F(DestroyObjectTests, SamplerNativeExplicit) {
+    SamplerDescriptor desc = {};
+
+    Ref<SamplerMock> samplerMock = AcquireRef(new SamplerMock(mDeviceMock, &desc));
+    EXPECT_CALL(*samplerMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(samplerMock->IsAlive());
+    samplerMock->Destroy();
+    EXPECT_FALSE(samplerMock->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, SamplerImplicit) {
+    SamplerDescriptor desc = {};
+
+    Ref<SamplerMock> samplerMock = AcquireRef(new SamplerMock(mDeviceMock, &desc));
+    EXPECT_CALL(*samplerMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(samplerMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateSamplerImpl)
+            .WillOnce(Return(ByMove(std::move(samplerMock))));
+        wgpu::Sampler sampler = device.CreateSampler(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(sampler.Get())->IsAlive());
+        EXPECT_TRUE(FromAPI(sampler.Get())->IsCachedReference());
+    }
+}
+
+TEST_F(DestroyObjectTests, ShaderModuleNativeExplicit) {
+    Ref<ShaderModuleMock> shaderModuleMock =
+        ShaderModuleMock::Create(mDeviceMock, kVertexShader.data());
+    EXPECT_CALL(*shaderModuleMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(shaderModuleMock->IsAlive());
+    shaderModuleMock->Destroy();
+    EXPECT_FALSE(shaderModuleMock->IsAlive());
+}
+
+TEST_F(DestroyObjectTests, ShaderModuleImplicit) {
+    ShaderModuleWGSLDescriptor wgslDesc = {};
+    wgslDesc.source = kVertexShader.data();
+    ShaderModuleDescriptor desc = {};
+    desc.nextInChain = &wgslDesc;
+
+    Ref<ShaderModuleMock> shaderModuleMock = ShaderModuleMock::Create(mDeviceMock, &desc);
+    EXPECT_CALL(*shaderModuleMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(shaderModuleMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateShaderModuleImpl)
+            .WillOnce(Return(ByMove(std::move(shaderModuleMock))));
+        wgpu::ShaderModule shaderModule = device.CreateShaderModule(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(shaderModule.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, TextureNativeExplicit) {
+    TextureDescriptor desc = {};
+    desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    desc.size.width = 1;
+    desc.size.height = 1;
+    desc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc));
+    EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_TRUE(textureMock->IsAlive());
+    textureMock->Destroy();
+    EXPECT_FALSE(textureMock->IsAlive());
+}
+
+TEST_F(DestroyObjectTests, TextureApiExplicit) {
+    TextureDescriptor desc = {};
+    desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    desc.size.width = 1;
+    desc.size.height = 1;
+    desc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc));
+    EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(ByMove(std::move(textureMock))));
+    wgpu::Texture texture = device.CreateTexture(ToCppAPI(&desc));
+
+    EXPECT_TRUE(FromAPI(texture.Get())->IsAlive());
+    texture.Destroy();
+    EXPECT_FALSE(FromAPI(texture.Get())->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, TextureImplicit) {
+    TextureDescriptor desc = {};
+    desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    desc.size.width = 1;
+    desc.size.height = 1;
+    desc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+    Ref<TextureMock> textureMock = AcquireRef(new TextureMock(mDeviceMock, &desc));
+    EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(textureMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateTextureImpl)
+            .WillOnce(Return(ByMove(std::move(textureMock))));
+        wgpu::Texture texture = device.CreateTexture(ToCppAPI(&desc));
+
+        EXPECT_TRUE(FromAPI(texture.Get())->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, TextureViewNativeExplicit) {
+    TextureDescriptor textureDesc = {};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    textureDesc.size.width = 1;
+    textureDesc.size.height = 1;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
+
+    TextureViewDescriptor desc = {};
+    desc.format = wgpu::TextureFormat::RGBA8Unorm;
+    {
+        // Explicitly destroy the texture view.
+        Ref<TextureViewMock> textureViewMock =
+            AcquireRef(new TextureViewMock(textureMock.Get(), &desc));
+        EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1);
+
+        EXPECT_TRUE(textureViewMock->IsAlive());
+        textureViewMock->Destroy();
+        EXPECT_FALSE(textureViewMock->IsAlive());
+    }
+    {
+        // Destroying the owning texture should cause the view to be destroyed as well.
+        Ref<TextureViewMock> textureViewMock =
+            AcquireRef(new TextureViewMock(textureMock.Get(), &desc));
+        EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1);
+
+        EXPECT_TRUE(textureViewMock->IsAlive());
+        textureMock->Destroy();
+        EXPECT_FALSE(textureViewMock->IsAlive());
+    }
+}
+
+TEST_F(DestroyObjectTests, TextureViewApiExplicit) {
+    TextureDescriptor textureDesc = {};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    textureDesc.size.width = 1;
+    textureDesc.size.height = 1;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
+
+    TextureViewDescriptor viewDesc = {};
+    viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureViewMock> textureViewMock =
+        AcquireRef(new TextureViewMock(textureMock.Get(), &viewDesc));
+    EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1);
+
+    EXPECT_CALL(*mDeviceMock, CreateTextureViewImpl(textureMock.Get(), _))
+        .WillOnce(Return(ByMove(std::move(textureViewMock))));
+    EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(ByMove(std::move(textureMock))));
+    wgpu::Texture texture = device.CreateTexture(ToCppAPI(&textureDesc));
+    wgpu::TextureView textureView = texture.CreateView(ToCppAPI(&viewDesc));
+
+    EXPECT_TRUE(FromAPI(textureView.Get())->IsAlive());
+    texture.Destroy();
+    EXPECT_FALSE(FromAPI(textureView.Get())->IsAlive());
+}
+
+// If the reference count on API objects reach 0, they should delete themselves. Note that GTest
+// will also complain if there is a memory leak.
+TEST_F(DestroyObjectTests, TextureViewImplicit) {
+    TextureDescriptor textureDesc = {};
+    textureDesc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+    textureDesc.size.width = 1;
+    textureDesc.size.height = 1;
+    textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+    Ref<TextureMock> textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &textureDesc));
+
+    TextureViewDescriptor viewDesc = {};
+    viewDesc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+    Ref<TextureViewMock> textureViewMock =
+        AcquireRef(new TextureViewMock(textureMock.Get(), &viewDesc));
+    EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1);
+    {
+        ScopedRawPtrExpectation scoped(textureViewMock.Get());
+
+        EXPECT_CALL(*mDeviceMock, CreateTextureViewImpl(textureMock.Get(), _))
+            .WillOnce(Return(ByMove(std::move(textureViewMock))));
+        EXPECT_CALL(*mDeviceMock, CreateTextureImpl)
+            .WillOnce(Return(ByMove(std::move(textureMock))));
+        wgpu::Texture texture = device.CreateTexture(ToCppAPI(&textureDesc));
+        wgpu::TextureView textureView = texture.CreateView(ToCppAPI(&viewDesc));
+
+        EXPECT_TRUE(FromAPI(textureView.Get())->IsAlive());
+    }
+}
+
+// Destroying the objects on the device explicitly should result in all created objects being
+// destroyed in order.
+TEST_F(DestroyObjectTests, DestroyObjectsApiExplicit) {
+    Ref<BindGroupMock> bindGroupMock;
+    wgpu::BindGroup bindGroup;
+    {
+        BindGroupDescriptor desc = {};
+        desc.layout = mDeviceMock->GetEmptyBindGroupLayoutMock();
+        desc.entryCount = 0;
+        desc.entries = nullptr;
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        bindGroupMock = AcquireRef(new BindGroupMock(mDeviceMock, &desc));
+        EXPECT_CALL(*mDeviceMock, CreateBindGroupImpl).WillOnce(Return(bindGroupMock));
+        bindGroup = device.CreateBindGroup(ToCppAPI(&desc));
+    }
+
+    Ref<BindGroupLayoutMock> bindGroupLayoutMock;
+    wgpu::BindGroupLayout bindGroupLayout;
+    {
+        // Use an non-empty bind group layout to avoid hitting the internal empty layout in the
+        // cache.
+        BindGroupLayoutDescriptor desc = {};
+        std::vector<BindGroupLayoutEntry> entries;
+        entries.push_back(utils::BindingLayoutEntryInitializationHelper(
+            0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform));
+        desc.entryCount = static_cast<uint32_t>(entries.size());
+        desc.entries = entries.data();
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        bindGroupLayoutMock = AcquireRef(new BindGroupLayoutMock(mDeviceMock, &desc));
+        EXPECT_CALL(*mDeviceMock, CreateBindGroupLayoutImpl).WillOnce(Return(bindGroupLayoutMock));
+        bindGroupLayout = device.CreateBindGroupLayout(ToCppAPI(&desc));
+    }
+
+    Ref<ShaderModuleMock> csModuleMock;
+    wgpu::ShaderModule csModule;
+    {
+        ShaderModuleWGSLDescriptor wgslDesc = {};
+        wgslDesc.source = kComputeShader.data();
+        ShaderModuleDescriptor desc = {};
+        desc.nextInChain = &wgslDesc;
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        csModuleMock = ShaderModuleMock::Create(mDeviceMock, &desc);
+        EXPECT_CALL(*mDeviceMock, CreateShaderModuleImpl).WillOnce(Return(csModuleMock));
+        csModule = device.CreateShaderModule(ToCppAPI(&desc));
+    }
+
+    Ref<ShaderModuleMock> vsModuleMock;
+    wgpu::ShaderModule vsModule;
+    {
+        ShaderModuleWGSLDescriptor wgslDesc = {};
+        wgslDesc.source = kVertexShader.data();
+        ShaderModuleDescriptor desc = {};
+        desc.nextInChain = &wgslDesc;
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        vsModuleMock = ShaderModuleMock::Create(mDeviceMock, &desc);
+        EXPECT_CALL(*mDeviceMock, CreateShaderModuleImpl).WillOnce(Return(vsModuleMock));
+        vsModule = device.CreateShaderModule(ToCppAPI(&desc));
+    }
+
+    Ref<BufferMock> bufferMock;
+    wgpu::Buffer buffer;
+    {
+        BufferDescriptor desc = {};
+        desc.size = 16;
+        desc.usage = wgpu::BufferUsage::Uniform;
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        bufferMock = AcquireRef(new BufferMock(mDeviceMock, &desc));
+        EXPECT_CALL(*mDeviceMock, CreateBufferImpl).WillOnce(Return(bufferMock));
+        buffer = device.CreateBuffer(ToCppAPI(&desc));
+    }
+
+    Ref<CommandBufferMock> commandBufferMock;
+    wgpu::CommandBuffer commandBuffer;
+    {
+        CommandEncoderDescriptor commandEncoderDesc = {};
+        wgpu::CommandEncoder commandEncoder =
+            device.CreateCommandEncoder(ToCppAPI(&commandEncoderDesc));
+
+        CommandBufferDescriptor commandBufferDesc = {};
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        commandBufferMock = AcquireRef(
+            new CommandBufferMock(mDeviceMock, FromAPI(commandEncoder.Get()), &commandBufferDesc));
+        EXPECT_CALL(*mDeviceMock, CreateCommandBuffer).WillOnce(Return(commandBufferMock));
+        commandBuffer = commandEncoder.Finish(ToCppAPI(&commandBufferDesc));
+    }
+
+    Ref<ComputePipelineMock> computePipelineMock;
+    wgpu::ComputePipeline computePipeline;
+    {
+        ComputePipelineDescriptor desc = {};
+        desc.compute.module = csModuleMock.Get();
+        desc.compute.entryPoint = "main";
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        computePipelineMock = ComputePipelineMock::Create(mDeviceMock, &desc);
+        EXPECT_CALL(*computePipelineMock.Get(), Initialize).Times(1);
+        EXPECT_CALL(*mDeviceMock, CreateUninitializedComputePipelineImpl)
+            .WillOnce(Return(computePipelineMock));
+        computePipeline = device.CreateComputePipeline(ToCppAPI(&desc));
+    }
+
+    Ref<PipelineLayoutMock> pipelineLayoutMock;
+    wgpu::PipelineLayout pipelineLayout;
+    {
+        PipelineLayoutDescriptor desc = {};
+        std::vector<BindGroupLayoutBase*> bindGroupLayouts;
+        bindGroupLayouts.push_back(mDeviceMock->GetEmptyBindGroupLayoutMock());
+        desc.bindGroupLayoutCount = static_cast<uint32_t>(bindGroupLayouts.size());
+        desc.bindGroupLayouts = bindGroupLayouts.data();
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        pipelineLayoutMock = AcquireRef(new PipelineLayoutMock(mDeviceMock, &desc));
+        EXPECT_CALL(*mDeviceMock, CreatePipelineLayoutImpl).WillOnce(Return(pipelineLayoutMock));
+        pipelineLayout = device.CreatePipelineLayout(ToCppAPI(&desc));
+    }
+
+    Ref<QuerySetMock> querySetMock;
+    wgpu::QuerySet querySet;
+    {
+        QuerySetDescriptor desc = {};
+        desc.type = wgpu::QueryType::Occlusion;
+        desc.count = 1;
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        querySetMock = AcquireRef(new QuerySetMock(mDeviceMock, &desc));
+        EXPECT_CALL(*mDeviceMock, CreateQuerySetImpl).WillOnce(Return(querySetMock));
+        querySet = device.CreateQuerySet(ToCppAPI(&desc));
+    }
+
+    Ref<RenderPipelineMock> renderPipelineMock;
+    wgpu::RenderPipeline renderPipeline;
+    {
+        RenderPipelineDescriptor desc = {};
+        desc.vertex.module = vsModuleMock.Get();
+        desc.vertex.entryPoint = "main";
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        renderPipelineMock = RenderPipelineMock::Create(mDeviceMock, &desc);
+        EXPECT_CALL(*renderPipelineMock.Get(), Initialize).Times(1);
+        EXPECT_CALL(*mDeviceMock, CreateUninitializedRenderPipelineImpl)
+            .WillOnce(Return(renderPipelineMock));
+        renderPipeline = device.CreateRenderPipeline(ToCppAPI(&desc));
+    }
+
+    Ref<SamplerMock> samplerMock;
+    wgpu::Sampler sampler;
+    {
+        SamplerDescriptor desc = {};
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        samplerMock = AcquireRef(new SamplerMock(mDeviceMock, &desc));
+        EXPECT_CALL(*mDeviceMock, CreateSamplerImpl).WillOnce(Return(samplerMock));
+        sampler = device.CreateSampler(ToCppAPI(&desc));
+    }
+
+    Ref<TextureMock> textureMock;
+    wgpu::Texture texture;
+    {
+        TextureDescriptor desc = {};
+        desc.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
+        desc.size.width = 1;
+        desc.size.height = 1;
+        desc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        textureMock = AcquireRef(new NiceMock<TextureMock>(mDeviceMock, &desc));
+        EXPECT_CALL(*mDeviceMock, CreateTextureImpl).WillOnce(Return(textureMock));
+        texture = device.CreateTexture(ToCppAPI(&desc));
+    }
+
+    Ref<TextureViewMock> textureViewMock;
+    wgpu::TextureView textureView;
+    {
+        TextureViewDescriptor desc = {};
+        desc.format = wgpu::TextureFormat::RGBA8Unorm;
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        textureViewMock = AcquireRef(new TextureViewMock(textureMock.Get(), &desc));
+        EXPECT_CALL(*mDeviceMock, CreateTextureViewImpl).WillOnce(Return(textureViewMock));
+        textureView = texture.CreateView(ToCppAPI(&desc));
+    }
+
+    Ref<ExternalTextureMock> externalTextureMock;
+    wgpu::ExternalTexture externalTexture;
+    {
+        ExternalTextureDescriptor desc = {};
+        std::array<float, 12> placeholderConstantArray;
+        desc.yuvToRgbConversionMatrix = placeholderConstantArray.data();
+        desc.gamutConversionMatrix = placeholderConstantArray.data();
+        desc.srcTransferFunctionParameters = placeholderConstantArray.data();
+        desc.dstTransferFunctionParameters = placeholderConstantArray.data();
+        desc.visibleSize = {1, 1};
+        desc.plane0 = textureViewMock.Get();
+
+        ScopedRawPtrExpectation scoped(mDeviceMock);
+        externalTextureMock = ExternalTextureMock::Create(mDeviceMock, &desc);
+        EXPECT_CALL(*mDeviceMock, CreateExternalTextureImpl).WillOnce(Return(externalTextureMock));
+        externalTexture = device.CreateExternalTexture(ToCppAPI(&desc));
+    }
+
+    {
+        InSequence seq;
+        EXPECT_CALL(*commandBufferMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*renderPipelineMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*computePipelineMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*pipelineLayoutMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*bindGroupMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*bindGroupLayoutMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*vsModuleMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*csModuleMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*externalTextureMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*textureMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*textureViewMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*querySetMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*samplerMock.Get(), DestroyImpl).Times(1);
+        EXPECT_CALL(*bufferMock.Get(), DestroyImpl).Times(1);
+    }
+
+    EXPECT_TRUE(FromAPI(bindGroup.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(bindGroupLayout.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(buffer.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(commandBuffer.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(computePipeline.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(externalTexture.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(pipelineLayout.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(querySet.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(renderPipeline.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(sampler.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(vsModule.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(csModule.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(texture.Get())->IsAlive());
+    EXPECT_TRUE(FromAPI(textureView.Get())->IsAlive());
+    device.Destroy();
+    EXPECT_FALSE(FromAPI(bindGroup.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(bindGroupLayout.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(buffer.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(commandBuffer.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(computePipeline.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(externalTexture.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(pipelineLayout.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(querySet.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(renderPipeline.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(sampler.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(vsModule.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(csModule.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(texture.Get())->IsAlive());
+    EXPECT_FALSE(FromAPI(textureView.Get())->IsAlive());
+}
+
 class DestroyObjectRegressionTests : public DawnNativeTest {};
 
 // LastRefInCommand* tests are regression test(s) for https://crbug.com/chromium/1318792. The
@@ -780,17 +1044,17 @@
 // CommandEncoder, that destroying the device still works as expected (and does not cause
 // double-free).
 TEST_F(DestroyObjectRegressionTests, LastRefInCommandRenderPipeline) {
-    utils::BasicRenderPass pass = utils::CreateBasicRenderPass(device, 1, 1);
+    ::utils::BasicRenderPass pass = ::utils::CreateBasicRenderPass(device, 1, 1);
 
-    utils::ComboRenderPassDescriptor passDesc{};
+    ::utils::ComboRenderPassDescriptor passDesc{};
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
     wgpu::RenderPassEncoder renderEncoder = encoder.BeginRenderPass(&pass.renderPassInfo);
 
-    utils::ComboRenderPipelineDescriptor pipelineDesc;
+    ::utils::ComboRenderPipelineDescriptor pipelineDesc;
     pipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
-    pipelineDesc.vertex.module = utils::CreateShaderModule(device, kVertexShader.data());
+    pipelineDesc.vertex.module = ::utils::CreateShaderModule(device, kVertexShader.data());
     pipelineDesc.vertex.entryPoint = "main";
-    pipelineDesc.cFragment.module = utils::CreateShaderModule(device, kFragmentShader.data());
+    pipelineDesc.cFragment.module = ::utils::CreateShaderModule(device, kFragmentShader.data());
     pipelineDesc.cFragment.entryPoint = "main";
     renderEncoder.SetPipeline(device.CreateRenderPipeline(&pipelineDesc));
 
@@ -805,7 +1069,7 @@
     wgpu::ComputePassEncoder computeEncoder = encoder.BeginComputePass();
 
     wgpu::ComputePipelineDescriptor pipelineDesc;
-    pipelineDesc.compute.module = utils::CreateShaderModule(device, kComputeShader.data());
+    pipelineDesc.compute.module = ::utils::CreateShaderModule(device, kComputeShader.data());
     pipelineDesc.compute.entryPoint = "main";
     computeEncoder.SetPipeline(device.CreateComputePipeline(&pipelineDesc));
 
diff --git a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp
index c2775d7..6a57746 100644
--- a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.cpp
@@ -16,10 +16,15 @@
 
 namespace dawn::native {
 
-BindGroupLayoutMock::BindGroupLayoutMock(DeviceBase* device) : BindGroupLayoutBase(device) {
+BindGroupLayoutMock::BindGroupLayoutMock(DeviceMock* device,
+                                         const BindGroupLayoutDescriptor* descriptor,
+                                         PipelineCompatibilityToken pipelineCompatibilityToken)
+    : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() {
         this->BindGroupLayoutBase::DestroyImpl();
     });
+
+    SetContentHash(ComputeContentHash());
 }
 
 BindGroupLayoutMock::~BindGroupLayoutMock() = default;
diff --git a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h
index eb0183c..cc38952 100644
--- a/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h
+++ b/src/dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h
@@ -18,13 +18,16 @@
 #include "gmock/gmock.h"
 
 #include "dawn/native/BindGroupLayout.h"
-#include "dawn/native/Device.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
-class BindGroupLayoutMock final : public BindGroupLayoutBase {
+class BindGroupLayoutMock : public BindGroupLayoutBase {
   public:
-    explicit BindGroupLayoutMock(DeviceBase* device);
+    BindGroupLayoutMock(
+        DeviceMock* device,
+        const BindGroupLayoutDescriptor* descriptor,
+        PipelineCompatibilityToken pipelineCompatibilityToken = PipelineCompatibilityToken(0));
     ~BindGroupLayoutMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/BindGroupMock.cpp b/src/dawn/tests/unittests/native/mocks/BindGroupMock.cpp
index ca6f851..81e6459 100644
--- a/src/dawn/tests/unittests/native/mocks/BindGroupMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/BindGroupMock.cpp
@@ -16,7 +16,9 @@
 
 namespace dawn::native {
 
-BindGroupMock::BindGroupMock(DeviceBase* device) : BindGroupBase(device) {
+BindGroupMock::BindGroupMock(DeviceMock* device, const BindGroupDescriptor* descriptor)
+    : BindGroupDataHolder(descriptor->layout->GetBindingDataSize()),
+      BindGroupBase(device, descriptor, mBindingDataAllocation) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->BindGroupBase::DestroyImpl(); });
 }
 
diff --git a/src/dawn/tests/unittests/native/mocks/BindGroupMock.h b/src/dawn/tests/unittests/native/mocks/BindGroupMock.h
index 0dd5e02..9c9efda 100644
--- a/src/dawn/tests/unittests/native/mocks/BindGroupMock.h
+++ b/src/dawn/tests/unittests/native/mocks/BindGroupMock.h
@@ -18,13 +18,14 @@
 #include "gmock/gmock.h"
 
 #include "dawn/native/BindGroup.h"
-#include "dawn/native/Device.h"
+#include "dawn/native/null/DeviceNull.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
-class BindGroupMock : public BindGroupBase {
+class BindGroupMock : private null::BindGroupDataHolder, public BindGroupBase {
   public:
-    explicit BindGroupMock(DeviceBase* device);
+    BindGroupMock(DeviceMock* device, const BindGroupDescriptor* descriptor);
     ~BindGroupMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/BufferMock.cpp b/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
index 98b345f..788a61b 100644
--- a/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/BufferMock.cpp
@@ -16,8 +16,8 @@
 
 namespace dawn::native {
 
-BufferMock::BufferMock(DeviceBase* device, BufferBase::BufferState state)
-    : BufferBase(device, state) {
+BufferMock::BufferMock(DeviceMock* device, const BufferDescriptor* descriptor)
+    : BufferBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->BufferBase::DestroyImpl(); });
 }
 
diff --git a/src/dawn/tests/unittests/native/mocks/BufferMock.h b/src/dawn/tests/unittests/native/mocks/BufferMock.h
index d98f31d..7ddfc1f 100644
--- a/src/dawn/tests/unittests/native/mocks/BufferMock.h
+++ b/src/dawn/tests/unittests/native/mocks/BufferMock.h
@@ -18,13 +18,13 @@
 #include "gmock/gmock.h"
 
 #include "dawn/native/Buffer.h"
-#include "dawn/native/Device.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class BufferMock : public BufferBase {
   public:
-    BufferMock(DeviceBase* device, BufferBase::BufferState state);
+    BufferMock(DeviceMock* device, const BufferDescriptor* descriptor);
     ~BufferMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/CommandBufferMock.cpp b/src/dawn/tests/unittests/native/mocks/CommandBufferMock.cpp
index 80c0cf3..579cdda 100644
--- a/src/dawn/tests/unittests/native/mocks/CommandBufferMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/CommandBufferMock.cpp
@@ -14,9 +14,18 @@
 
 #include "dawn/tests/unittests/native/mocks/CommandBufferMock.h"
 
+#include "dawn/native/CommandEncoder.h"
+
 namespace dawn::native {
 
-CommandBufferMock::CommandBufferMock(DeviceBase* device) : CommandBufferBase(device) {
+CommandBufferMock::CommandBufferMock(DeviceMock* device,
+                                     CommandEncoder* encoder,
+                                     const CommandBufferDescriptor* descriptor)
+    : CommandBufferBase(encoder, descriptor) {
+    // Make sure that the command encoder was also created using the mock device since it is not
+    // directly passed in.
+    ASSERT(device == encoder->GetDevice());
+
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->CommandBufferBase::DestroyImpl(); });
 }
 
diff --git a/src/dawn/tests/unittests/native/mocks/CommandBufferMock.h b/src/dawn/tests/unittests/native/mocks/CommandBufferMock.h
index f15a46b..04669d5 100644
--- a/src/dawn/tests/unittests/native/mocks/CommandBufferMock.h
+++ b/src/dawn/tests/unittests/native/mocks/CommandBufferMock.h
@@ -18,13 +18,15 @@
 #include "gmock/gmock.h"
 
 #include "dawn/native/CommandBuffer.h"
-#include "dawn/native/Device.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class CommandBufferMock : public CommandBufferBase {
   public:
-    explicit CommandBufferMock(DeviceBase* device);
+    CommandBufferMock(DeviceMock* device,
+                      CommandEncoder* encoder,
+                      const CommandBufferDescriptor* descriptor);
     ~CommandBufferMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp
index 2828456..97280ef 100644
--- a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.cpp
@@ -16,7 +16,12 @@
 
 namespace dawn::native {
 
-ComputePipelineMock::ComputePipelineMock(DeviceBase* device) : ComputePipelineBase(device) {
+using ::testing::NiceMock;
+
+ComputePipelineMock::ComputePipelineMock(DeviceBase* device,
+                                         const ComputePipelineDescriptor* descriptor)
+    : ComputePipelineBase(device, descriptor) {
+    ON_CALL(*this, Initialize).WillByDefault([]() -> MaybeError { return {}; });
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() {
         this->ComputePipelineBase::DestroyImpl();
     });
@@ -24,4 +29,14 @@
 
 ComputePipelineMock::~ComputePipelineMock() = default;
 
+// static
+Ref<ComputePipelineMock> ComputePipelineMock::Create(DeviceMock* device,
+                                                     const ComputePipelineDescriptor* descriptor) {
+    ComputePipelineDescriptor appliedDescriptor;
+    Ref<PipelineLayoutBase> layoutRef = ValidateLayoutAndGetComputePipelineDescriptorWithDefaults(
+                                            device, *descriptor, &appliedDescriptor)
+                                            .AcquireSuccess();
+    return AcquireRef(new NiceMock<ComputePipelineMock>(device, &appliedDescriptor));
+}
+
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h
index 404359a..d624a55 100644
--- a/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h
+++ b/src/dawn/tests/unittests/native/mocks/ComputePipelineMock.h
@@ -18,18 +18,23 @@
 #include "gmock/gmock.h"
 
 #include "dawn/native/ComputePipeline.h"
-#include "dawn/native/Device.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class ComputePipelineMock : public ComputePipelineBase {
   public:
-    explicit ComputePipelineMock(DeviceBase* device);
+    // Creates a compute pipeline given the descriptor.
+    static Ref<ComputePipelineMock> Create(DeviceMock* device,
+                                           const ComputePipelineDescriptor* descriptor);
+
     ~ComputePipelineMock() override;
 
     MOCK_METHOD(MaybeError, Initialize, (), (override));
-    MOCK_METHOD(size_t, ComputeContentHash, (), (override));
     MOCK_METHOD(void, DestroyImpl, (), (override));
+
+  protected:
+    ComputePipelineMock(DeviceBase* device, const ComputePipelineDescriptor* descriptor);
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/DawnMockTest.cpp b/src/dawn/tests/unittests/native/mocks/DawnMockTest.cpp
new file mode 100644
index 0000000..3acd5b5
--- /dev/null
+++ b/src/dawn/tests/unittests/native/mocks/DawnMockTest.cpp
@@ -0,0 +1,33 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/tests/unittests/native/mocks/DawnMockTest.h"
+
+#include "dawn/dawn_proc.h"
+
+namespace dawn::native {
+
+DawnMockTest::DawnMockTest() {
+    dawnProcSetProcs(&dawn::native::GetProcs());
+
+    mDeviceMock = new ::testing::NiceMock<DeviceMock>();
+    device = wgpu::Device::Acquire(ToAPI(mDeviceMock));
+}
+
+DawnMockTest::~DawnMockTest() {
+    device = wgpu::Device();
+    dawnProcSetProcs(nullptr);
+}
+
+}  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/DawnMockTest.h b/src/dawn/tests/unittests/native/mocks/DawnMockTest.h
new file mode 100644
index 0000000..e84662d
--- /dev/null
+++ b/src/dawn/tests/unittests/native/mocks/DawnMockTest.h
@@ -0,0 +1,32 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
+#include "dawn/webgpu_cpp.h"
+
+namespace dawn::native {
+
+class DawnMockTest : public ::testing::Test {
+  public:
+    DawnMockTest();
+    ~DawnMockTest() override;
+
+  protected:
+    ::testing::NiceMock<DeviceMock>* mDeviceMock;
+    wgpu::Device device;
+};
+
+}  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp b/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
new file mode 100644
index 0000000..e701c8b
--- /dev/null
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.cpp
@@ -0,0 +1,125 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
+
+#include "dawn/tests/unittests/native/mocks/BindGroupLayoutMock.h"
+#include "dawn/tests/unittests/native/mocks/BindGroupMock.h"
+#include "dawn/tests/unittests/native/mocks/BufferMock.h"
+#include "dawn/tests/unittests/native/mocks/ComputePipelineMock.h"
+#include "dawn/tests/unittests/native/mocks/ExternalTextureMock.h"
+#include "dawn/tests/unittests/native/mocks/PipelineLayoutMock.h"
+#include "dawn/tests/unittests/native/mocks/QuerySetMock.h"
+#include "dawn/tests/unittests/native/mocks/QueueMock.h"
+#include "dawn/tests/unittests/native/mocks/RenderPipelineMock.h"
+#include "dawn/tests/unittests/native/mocks/SamplerMock.h"
+#include "dawn/tests/unittests/native/mocks/ShaderModuleMock.h"
+#include "dawn/tests/unittests/native/mocks/TextureMock.h"
+
+namespace dawn::native {
+
+using ::testing::NiceMock;
+using ::testing::WithArgs;
+
+DeviceMock::DeviceMock() {
+    mInstance = InstanceBase::Create();
+
+    // Set all default creation functions to return nice mock objects.
+    ON_CALL(*this, CreateBindGroupImpl)
+        .WillByDefault(WithArgs<0>(
+            [this](const BindGroupDescriptor* descriptor) -> ResultOrError<Ref<BindGroupBase>> {
+                return AcquireRef(new NiceMock<BindGroupMock>(this, descriptor));
+            }));
+    ON_CALL(*this, CreateBindGroupLayoutImpl)
+        .WillByDefault(WithArgs<0, 1>([this](const BindGroupLayoutDescriptor* descriptor,
+                                             PipelineCompatibilityToken pipelineCompatibilityToken)
+                                          -> ResultOrError<Ref<BindGroupLayoutBase>> {
+            return AcquireRef(
+                new NiceMock<BindGroupLayoutMock>(this, descriptor, pipelineCompatibilityToken));
+        }));
+    ON_CALL(*this, CreateBufferImpl)
+        .WillByDefault(WithArgs<0>(
+            [this](const BufferDescriptor* descriptor) -> ResultOrError<Ref<BufferBase>> {
+                return AcquireRef(new NiceMock<BufferMock>(this, descriptor));
+            }));
+    ON_CALL(*this, CreateExternalTextureImpl)
+        .WillByDefault(WithArgs<0>([this](const ExternalTextureDescriptor* descriptor)
+                                       -> ResultOrError<Ref<ExternalTextureBase>> {
+            return ExternalTextureMock::Create(this, descriptor);
+        }));
+    ON_CALL(*this, CreatePipelineLayoutImpl)
+        .WillByDefault(WithArgs<0>([this](const PipelineLayoutDescriptor* descriptor)
+                                       -> ResultOrError<Ref<PipelineLayoutBase>> {
+            return AcquireRef(new NiceMock<PipelineLayoutMock>(this, descriptor));
+        }));
+    ON_CALL(*this, CreateQuerySetImpl)
+        .WillByDefault(WithArgs<0>(
+            [this](const QuerySetDescriptor* descriptor) -> ResultOrError<Ref<QuerySetBase>> {
+                return AcquireRef(new NiceMock<QuerySetMock>(this, descriptor));
+            }));
+    ON_CALL(*this, CreateSamplerImpl)
+        .WillByDefault(WithArgs<0>(
+            [this](const SamplerDescriptor* descriptor) -> ResultOrError<Ref<SamplerBase>> {
+                return AcquireRef(new NiceMock<SamplerMock>(this, descriptor));
+            }));
+    ON_CALL(*this, CreateShaderModuleImpl)
+        .WillByDefault(WithArgs<0>([this](const ShaderModuleDescriptor* descriptor)
+                                       -> ResultOrError<Ref<ShaderModuleBase>> {
+            return ShaderModuleMock::Create(this, descriptor);
+        }));
+    ON_CALL(*this, CreateTextureImpl)
+        .WillByDefault(WithArgs<0>(
+            [this](const TextureDescriptor* descriptor) -> ResultOrError<Ref<TextureBase>> {
+                return AcquireRef(new NiceMock<TextureMock>(this, descriptor));
+            }));
+    ON_CALL(*this, CreateTextureViewImpl)
+        .WillByDefault(WithArgs<0, 1>(
+            [](TextureBase* texture,
+               const TextureViewDescriptor* descriptor) -> ResultOrError<Ref<TextureViewBase>> {
+                return AcquireRef(new NiceMock<TextureViewMock>(texture, descriptor));
+            }));
+    ON_CALL(*this, CreateUninitializedComputePipelineImpl)
+        .WillByDefault(WithArgs<0>(
+            [this](const ComputePipelineDescriptor* descriptor) -> Ref<ComputePipelineBase> {
+                return ComputePipelineMock::Create(this, descriptor);
+            }));
+    ON_CALL(*this, CreateUninitializedRenderPipelineImpl)
+        .WillByDefault(WithArgs<0>(
+            [this](const RenderPipelineDescriptor* descriptor) -> Ref<RenderPipelineBase> {
+                return RenderPipelineMock::Create(this, descriptor);
+            }));
+
+    // By default, the mock's TickImpl will succeed.
+    ON_CALL(*this, TickImpl).WillByDefault([]() -> MaybeError { return {}; });
+
+    // Initialize the device.
+    QueueDescriptor desc = {};
+    EXPECT_FALSE(Initialize(AcquireRef(new NiceMock<QueueMock>(this, &desc))).IsError());
+}
+
+DeviceMock::~DeviceMock() = default;
+
+dawn::platform::Platform* DeviceMock::GetPlatform() const {
+    return mInstance->GetPlatform();
+}
+
+QueueMock* DeviceMock::GetQueueMock() {
+    return reinterpret_cast<QueueMock*>(GetQueue());
+}
+
+BindGroupLayoutMock* DeviceMock::GetEmptyBindGroupLayoutMock() {
+    return reinterpret_cast<BindGroupLayoutMock*>(GetEmptyBindGroupLayout());
+}
+
+}  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/DeviceMock.h b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
index d4aa224..681a936 100644
--- a/src/dawn/tests/unittests/native/mocks/DeviceMock.h
+++ b/src/dawn/tests/unittests/native/mocks/DeviceMock.h
@@ -18,17 +18,34 @@
 #include <memory>
 
 #include "dawn/native/Device.h"
+#include "dawn/native/Instance.h"
 #include "dawn/native/RenderPipeline.h"
+#include "dawn/platform/DawnPlatform.h"
+#include "dawn/tests/unittests/native/mocks/QueueMock.h"
 #include "gmock/gmock.h"
 
 namespace dawn::native {
 
+class BindGroupLayoutMock;
+
 class DeviceMock : public DeviceBase {
   public:
     // Exposes some protected functions for testing purposes.
     using DeviceBase::DestroyObjects;
     using DeviceBase::ForceSetToggleForTesting;
 
+    // TODO(lokokung): Use real DeviceBase constructor instead of mock specific one.
+    //       - Requires AdapterMock.
+    //       - Can probably remove GetPlatform overload.
+    //       - Allows removing ForceSetToggleForTesting calls.
+    DeviceMock();
+    ~DeviceMock() override;
+    dawn::platform::Platform* GetPlatform() const override;
+
+    // Mock specific functionality.
+    QueueMock* GetQueueMock();
+    BindGroupLayoutMock* GetEmptyBindGroupLayoutMock();
+
     MOCK_METHOD(ResultOrError<Ref<CommandBufferBase>>,
                 CreateCommandBuffer,
                 (CommandEncoder*, const CommandBufferDescriptor*),
@@ -114,6 +131,9 @@
     MOCK_METHOD(void, DestroyImpl, (), (override));
     MOCK_METHOD(MaybeError, WaitForIdleForDestruction, (), (override));
     MOCK_METHOD(bool, HasPendingCommands, (), (const, override));
+
+  private:
+    Ref<InstanceBase> mInstance;
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.cpp b/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.cpp
index d0e0f23..0b96a84 100644
--- a/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.cpp
@@ -16,7 +16,11 @@
 
 namespace dawn::native {
 
-ExternalTextureMock::ExternalTextureMock(DeviceBase* device) : ExternalTextureBase(device) {
+using ::testing::NiceMock;
+
+ExternalTextureMock::ExternalTextureMock(DeviceMock* device,
+                                         const ExternalTextureDescriptor* descriptor)
+    : ExternalTextureBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() {
         this->ExternalTextureBase::DestroyImpl();
     });
@@ -24,4 +28,14 @@
 
 ExternalTextureMock::~ExternalTextureMock() = default;
 
+// static
+Ref<ExternalTextureMock> ExternalTextureMock::Create(DeviceMock* device,
+                                                     const ExternalTextureDescriptor* descriptor) {
+    Ref<ExternalTextureMock> externalTexture =
+        AcquireRef(new NiceMock<ExternalTextureMock>(device, descriptor));
+
+    externalTexture->Initialize(device, descriptor).AcquireSuccess();
+    return externalTexture;
+}
+
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.h b/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.h
index 55096da..b8a4f5c 100644
--- a/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.h
+++ b/src/dawn/tests/unittests/native/mocks/ExternalTextureMock.h
@@ -17,17 +17,23 @@
 
 #include "gmock/gmock.h"
 
-#include "dawn/native/Device.h"
 #include "dawn/native/ExternalTexture.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class ExternalTextureMock : public ExternalTextureBase {
   public:
-    explicit ExternalTextureMock(DeviceBase* device);
+    // Creates an external texture mock based on a descriptor.
+    static Ref<ExternalTextureMock> Create(DeviceMock* device,
+                                           const ExternalTextureDescriptor* descriptor);
+
     ~ExternalTextureMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
+
+  protected:
+    ExternalTextureMock(DeviceMock* device, const ExternalTextureDescriptor* descriptor);
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp
index 191a03f..091450f 100644
--- a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.cpp
@@ -16,10 +16,14 @@
 
 namespace dawn::native {
 
-PipelineLayoutMock::PipelineLayoutMock(DeviceBase* device) : PipelineLayoutBase(device) {
+PipelineLayoutMock::PipelineLayoutMock(DeviceMock* device,
+                                       const PipelineLayoutDescriptor* descriptor)
+    : PipelineLayoutBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() {
         this->PipelineLayoutBase::DestroyImpl();
     });
+
+    SetContentHash(ComputeContentHash());
 }
 
 PipelineLayoutMock::~PipelineLayoutMock() = default;
diff --git a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h
index f00bcf4..30cda7c 100644
--- a/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h
+++ b/src/dawn/tests/unittests/native/mocks/PipelineLayoutMock.h
@@ -17,14 +17,14 @@
 
 #include "gmock/gmock.h"
 
-#include "dawn/native/Device.h"
 #include "dawn/native/PipelineLayout.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class PipelineLayoutMock : public PipelineLayoutBase {
   public:
-    explicit PipelineLayoutMock(DeviceBase* device);
+    PipelineLayoutMock(DeviceMock* device, const PipelineLayoutDescriptor* descriptor);
     ~PipelineLayoutMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/QuerySetMock.cpp b/src/dawn/tests/unittests/native/mocks/QuerySetMock.cpp
index 5657f86..6ba771e 100644
--- a/src/dawn/tests/unittests/native/mocks/QuerySetMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/QuerySetMock.cpp
@@ -16,7 +16,8 @@
 
 namespace dawn::native {
 
-QuerySetMock::QuerySetMock(DeviceBase* device) : QuerySetBase(device) {
+QuerySetMock::QuerySetMock(DeviceMock* device, const QuerySetDescriptor* descriptor)
+    : QuerySetBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->QuerySetBase::DestroyImpl(); });
 }
 
diff --git a/src/dawn/tests/unittests/native/mocks/QuerySetMock.h b/src/dawn/tests/unittests/native/mocks/QuerySetMock.h
index 62ba31b..3af393f 100644
--- a/src/dawn/tests/unittests/native/mocks/QuerySetMock.h
+++ b/src/dawn/tests/unittests/native/mocks/QuerySetMock.h
@@ -17,14 +17,14 @@
 
 #include "gmock/gmock.h"
 
-#include "dawn/native/Device.h"
 #include "dawn/native/QuerySet.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class QuerySetMock : public QuerySetBase {
   public:
-    explicit QuerySetMock(DeviceBase* device);
+    QuerySetMock(DeviceMock* device, const QuerySetDescriptor* descriptor);
     ~QuerySetMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/QueueMock.cpp b/src/dawn/tests/unittests/native/mocks/QueueMock.cpp
new file mode 100644
index 0000000..f95d055
--- /dev/null
+++ b/src/dawn/tests/unittests/native/mocks/QueueMock.cpp
@@ -0,0 +1,28 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "dawn/tests/unittests/native/mocks/QueueMock.h"
+
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
+
+namespace dawn::native {
+
+QueueMock::QueueMock(DeviceMock* device, const QueueDescriptor* descriptor)
+    : QueueBase(device, descriptor) {
+    ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->QueueBase::DestroyImpl(); });
+}
+
+QueueMock::~QueueMock() = default;
+
+}  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/QueueMock.h b/src/dawn/tests/unittests/native/mocks/QueueMock.h
new file mode 100644
index 0000000..02b0c17
--- /dev/null
+++ b/src/dawn/tests/unittests/native/mocks/QueueMock.h
@@ -0,0 +1,45 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_QUEUEMOCK_H_
+#define SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_QUEUEMOCK_H_
+
+#include "gmock/gmock.h"
+
+#include "dawn/native/Queue.h"
+
+namespace dawn::native {
+
+class DeviceMock;
+
+class QueueMock : public QueueBase {
+  public:
+    QueueMock(DeviceMock* device, const QueueDescriptor* descriptor);
+    ~QueueMock() override;
+
+    MOCK_METHOD(MaybeError, SubmitImpl, (uint32_t, CommandBufferBase* const*), (override));
+    MOCK_METHOD(MaybeError,
+                WriteBufferImpl,
+                (BufferBase*, uint64_t, const void*, size_t),
+                (override));
+    MOCK_METHOD(MaybeError,
+                WriteTextureImpl,
+                (const ImageCopyTexture&, const void*, const TextureDataLayout&, const Extent3D&),
+                (override));
+    MOCK_METHOD(void, DestroyImpl, (), (override));
+};
+
+}  // namespace dawn::native
+
+#endif  // SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_QUEUEMOCK_H_
diff --git a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp
index cf8a861..e2c500e 100644
--- a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.cpp
@@ -16,7 +16,12 @@
 
 namespace dawn::native {
 
-RenderPipelineMock::RenderPipelineMock(DeviceBase* device) : RenderPipelineBase(device) {
+using ::testing::NiceMock;
+
+RenderPipelineMock::RenderPipelineMock(DeviceMock* device,
+                                       const RenderPipelineDescriptor* descriptor)
+    : RenderPipelineBase(device, descriptor) {
+    ON_CALL(*this, Initialize).WillByDefault([]() -> MaybeError { return {}; });
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() {
         this->RenderPipelineBase::DestroyImpl();
     });
@@ -24,4 +29,14 @@
 
 RenderPipelineMock::~RenderPipelineMock() = default;
 
+// static
+Ref<RenderPipelineMock> RenderPipelineMock::Create(DeviceMock* device,
+                                                   const RenderPipelineDescriptor* descriptor) {
+    RenderPipelineDescriptor appliedDescriptor;
+    Ref<PipelineLayoutBase> layoutRef = ValidateLayoutAndGetRenderPipelineDescriptorWithDefaults(
+                                            device, *descriptor, &appliedDescriptor)
+                                            .AcquireSuccess();
+    return AcquireRef(new NiceMock<RenderPipelineMock>(device, &appliedDescriptor));
+}
+
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h
index 1558b4b..14fe2c8 100644
--- a/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h
+++ b/src/dawn/tests/unittests/native/mocks/RenderPipelineMock.h
@@ -17,19 +17,24 @@
 
 #include "gmock/gmock.h"
 
-#include "dawn/native/Device.h"
 #include "dawn/native/RenderPipeline.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class RenderPipelineMock : public RenderPipelineBase {
   public:
-    explicit RenderPipelineMock(DeviceBase* device);
+    // Creates a compute pipeline given the descriptor.
+    static Ref<RenderPipelineMock> Create(DeviceMock* device,
+                                          const RenderPipelineDescriptor* descriptor);
+
     ~RenderPipelineMock() override;
 
     MOCK_METHOD(MaybeError, Initialize, (), (override));
-    MOCK_METHOD(size_t, ComputeContentHash, (), (override));
     MOCK_METHOD(void, DestroyImpl, (), (override));
+
+  protected:
+    RenderPipelineMock(DeviceMock* device, const RenderPipelineDescriptor* descriptor);
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/SamplerMock.cpp b/src/dawn/tests/unittests/native/mocks/SamplerMock.cpp
index 191addb..c4249cd 100644
--- a/src/dawn/tests/unittests/native/mocks/SamplerMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/SamplerMock.cpp
@@ -16,8 +16,11 @@
 
 namespace dawn::native {
 
-SamplerMock::SamplerMock(DeviceBase* device) : SamplerBase(device) {
+SamplerMock::SamplerMock(DeviceMock* device, const SamplerDescriptor* descriptor)
+    : SamplerBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->SamplerBase::DestroyImpl(); });
+
+    SetContentHash(ComputeContentHash());
 }
 
 SamplerMock::~SamplerMock() = default;
diff --git a/src/dawn/tests/unittests/native/mocks/SamplerMock.h b/src/dawn/tests/unittests/native/mocks/SamplerMock.h
index fc378e8..c0ff32e 100644
--- a/src/dawn/tests/unittests/native/mocks/SamplerMock.h
+++ b/src/dawn/tests/unittests/native/mocks/SamplerMock.h
@@ -17,14 +17,14 @@
 
 #include "gmock/gmock.h"
 
-#include "dawn/native/Device.h"
 #include "dawn/native/Sampler.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class SamplerMock : public SamplerBase {
   public:
-    explicit SamplerMock(DeviceBase* device);
+    SamplerMock(DeviceMock* device, const SamplerDescriptor* descriptor);
     ~SamplerMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
diff --git a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
index 6cf6a13..e21add2 100644
--- a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.cpp
@@ -14,27 +14,39 @@
 
 #include "dawn/tests/unittests/native/mocks/ShaderModuleMock.h"
 
+#include "dawn/native/ChainUtils_autogen.h"
+
 namespace dawn::native {
 
-ShaderModuleMock::ShaderModuleMock(DeviceBase* device) : ShaderModuleBase(device) {
+using ::testing::NiceMock;
+
+ShaderModuleMock::ShaderModuleMock(DeviceMock* device, const ShaderModuleDescriptor* descriptor)
+    : ShaderModuleBase(device, descriptor) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->ShaderModuleBase::DestroyImpl(); });
+
+    SetContentHash(ComputeContentHash());
 }
 
 ShaderModuleMock::~ShaderModuleMock() = default;
 
-ResultOrError<Ref<ShaderModuleMock>> ShaderModuleMock::Create(DeviceBase* device,
-                                                              const char* source) {
-    ShaderModuleMock* mock = new ShaderModuleMock(device);
-
-    ShaderModuleWGSLDescriptor wgslDesc;
-    wgslDesc.source = source;
-    ShaderModuleDescriptor desc;
-    desc.nextInChain = &wgslDesc;
-
+// static
+Ref<ShaderModuleMock> ShaderModuleMock::Create(DeviceMock* device,
+                                               const ShaderModuleDescriptor* descriptor) {
+    Ref<ShaderModuleMock> shaderModule =
+        AcquireRef(new NiceMock<ShaderModuleMock>(device, descriptor));
     ShaderModuleParseResult parseResult;
-    DAWN_TRY(ValidateAndParseShaderModule(device, &desc, &parseResult, nullptr));
-    DAWN_TRY(mock->InitializeBase(&parseResult, nullptr));
-    return AcquireRef(mock);
+    ValidateAndParseShaderModule(device, descriptor, &parseResult, nullptr).AcquireSuccess();
+    shaderModule->InitializeBase(&parseResult, nullptr).AcquireSuccess();
+    return shaderModule;
+}
+
+// static
+Ref<ShaderModuleMock> ShaderModuleMock::Create(DeviceMock* device, const char* source) {
+    ShaderModuleWGSLDescriptor wgslDesc = {};
+    wgslDesc.source = source;
+    ShaderModuleDescriptor desc = {};
+    desc.nextInChain = &wgslDesc;
+    return Create(device, &desc);
 }
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h
index 3281e07..6eaa57f 100644
--- a/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h
+++ b/src/dawn/tests/unittests/native/mocks/ShaderModuleMock.h
@@ -15,25 +15,26 @@
 #ifndef SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_SHADERMODULEMOCK_H_
 #define SRC_DAWN_TESTS_UNITTESTS_NATIVE_MOCKS_SHADERMODULEMOCK_H_
 
-#include <memory>
-
 #include "gmock/gmock.h"
 
-#include "dawn/native/Device.h"
-#include "dawn/native/Error.h"
 #include "dawn/native/ShaderModule.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class ShaderModuleMock : public ShaderModuleBase {
   public:
-    explicit ShaderModuleMock(DeviceBase* device);
+    // Creates a shader module mock based on a descriptor or wgsl source.
+    static Ref<ShaderModuleMock> Create(DeviceMock* device,
+                                        const ShaderModuleDescriptor* descriptor);
+    static Ref<ShaderModuleMock> Create(DeviceMock* device, const char* source);
+
     ~ShaderModuleMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
 
-    // Creates a shader module mock based on the wgsl source.
-    static ResultOrError<Ref<ShaderModuleMock>> Create(DeviceBase* device, const char* source);
+  protected:
+    ShaderModuleMock(DeviceMock* device, const ShaderModuleDescriptor* descriptor);
 };
 
 }  // namespace dawn::native
diff --git a/src/dawn/tests/unittests/native/mocks/TextureMock.cpp b/src/dawn/tests/unittests/native/mocks/TextureMock.cpp
index ce782a6..0980509 100644
--- a/src/dawn/tests/unittests/native/mocks/TextureMock.cpp
+++ b/src/dawn/tests/unittests/native/mocks/TextureMock.cpp
@@ -16,14 +16,19 @@
 
 namespace dawn::native {
 
-TextureMock::TextureMock(DeviceBase* device, TextureBase::TextureState state)
-    : TextureBase(device, state) {
+TextureMock::TextureMock(DeviceMock* device,
+                         const TextureDescriptor* descriptor,
+                         TextureBase::TextureState state)
+    : TextureBase(device, descriptor, state) {
     ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->TextureBase::DestroyImpl(); });
 }
 
 TextureMock::~TextureMock() = default;
 
-TextureViewMock::TextureViewMock(TextureBase* texture) : TextureViewBase(texture) {}
+TextureViewMock::TextureViewMock(TextureBase* texture, const TextureViewDescriptor* descriptor)
+    : TextureViewBase(texture, descriptor) {
+    ON_CALL(*this, DestroyImpl).WillByDefault([this]() { this->TextureViewBase::DestroyImpl(); });
+}
 
 TextureViewMock::~TextureViewMock() = default;
 
diff --git a/src/dawn/tests/unittests/native/mocks/TextureMock.h b/src/dawn/tests/unittests/native/mocks/TextureMock.h
index 20bd928..93a819f 100644
--- a/src/dawn/tests/unittests/native/mocks/TextureMock.h
+++ b/src/dawn/tests/unittests/native/mocks/TextureMock.h
@@ -17,14 +17,16 @@
 
 #include "gmock/gmock.h"
 
-#include "dawn/native/Device.h"
 #include "dawn/native/Texture.h"
+#include "dawn/tests/unittests/native/mocks/DeviceMock.h"
 
 namespace dawn::native {
 
 class TextureMock : public TextureBase {
   public:
-    TextureMock(DeviceBase* device, TextureBase::TextureState state);
+    TextureMock(DeviceMock* device,
+                const TextureDescriptor* descriptor,
+                TextureBase::TextureState state = TextureBase::TextureState::OwnedInternal);
     ~TextureMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));
@@ -32,7 +34,7 @@
 
 class TextureViewMock : public TextureViewBase {
   public:
-    explicit TextureViewMock(TextureBase* texture);
+    TextureViewMock(TextureBase* texture, const TextureViewDescriptor* descriptor);
     ~TextureViewMock() override;
 
     MOCK_METHOD(void, DestroyImpl, (), (override));