Move noop subclasses into utils

We had the idea of a "noop" factory or renderer in a few places. Centralize these in "utils".

Involved moving the specialized noorenderpath into its test file.

Diffs=
ec44ae50d Move noop subclasses into utils
diff --git a/.rive_head b/.rive_head
index 87d3f8d..be0bf2a 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-034d4f8330faf92f2db19eef27e834425f5300ca
+ec44ae50d2c31782b8e3713033f94adc85625d90
diff --git a/dev/test/premake5.lua b/dev/test/premake5.lua
index c0d716b..1e3cb63 100644
--- a/dev/test/premake5.lua
+++ b/dev/test/premake5.lua
@@ -84,8 +84,10 @@
 
 includedirs {"./include", "../../include"}
 
-files {"../../src/**.cpp", -- the Rive runtime source
-"../../test/**.cpp" -- the tests
+files {
+    "../../src/**.cpp",   -- the Rive runtime source
+    "../../test/**.cpp",  -- the tests
+    "../../utils/**.cpp", -- no_op utils
 }
 
 defines {"TESTING", "ENABLE_QUERY_FLAT_VERTICES"}
@@ -97,4 +99,4 @@
 filter "system:windows"
     architecture "x64"
     defines {"_USE_MATH_DEFINES"}
-    buildoptions {WINDOWS_CLANG_CL_SUPPRESSED_WARNINGS}
\ No newline at end of file
+    buildoptions {WINDOWS_CLANG_CL_SUPPRESSED_WARNINGS}
diff --git a/test/no_op_factory.hpp b/include/utils/no_op_factory.hpp
similarity index 93%
rename from test/no_op_factory.hpp
rename to include/utils/no_op_factory.hpp
index f0f7330..32c83b6 100644
--- a/test/no_op_factory.hpp
+++ b/include/utils/no_op_factory.hpp
@@ -1,8 +1,11 @@
+/*
+ * Copyright 2022 Rive
+ */
+
 #ifndef _RIVE_NOOP_FACTORY_HPP_
 #define _RIVE_NOOP_FACTORY_HPP_
 
-#include <rive/renderer.hpp>
-#include <rive/factory.hpp>
+#include "rive/factory.hpp"
 
 namespace rive {
 
@@ -35,8 +38,5 @@
 
         std::unique_ptr<RenderImage> decodeImage(Span<const uint8_t>) override;
     };
-
-    static NoOpFactory gNoOpFactory;
-
 } // namespace rive
 #endif
diff --git a/include/utils/no_op_renderer.hpp b/include/utils/no_op_renderer.hpp
new file mode 100644
index 0000000..468e585
--- /dev/null
+++ b/include/utils/no_op_renderer.hpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_NOOP_RENDERER_HPP_
+#define _RIVE_NOOP_RENDERER_HPP_
+
+#include "rive/renderer.hpp"
+
+namespace rive {
+
+    class NoOpRenderer : public Renderer {
+    public:
+        void save() override {}
+        void restore() override {}
+        void transform(const Mat2D&) override {}
+        void drawPath(RenderPath* path, RenderPaint* paint) override {}
+        void clipPath(RenderPath* path) override {}
+        void drawImage(const RenderImage*, BlendMode, float) override {}
+        void drawImageMesh(const RenderImage*,
+                           rcp<RenderBuffer>,
+                           rcp<RenderBuffer>,
+                           rcp<RenderBuffer>,
+                           BlendMode,
+                           float) override {}
+    };
+
+} // namespace rive
+
+#endif
diff --git a/rivinfo/build/premake5.lua b/rivinfo/build/premake5.lua
index 0c69b56..897904e 100644
--- a/rivinfo/build/premake5.lua
+++ b/rivinfo/build/premake5.lua
@@ -43,7 +43,7 @@
 
     files {
         "../**.cpp",
-        "../../test/no_op_factory.cpp",
+        "../../utils/no_op_factory.cpp",
     }
 
     buildoptions {"-Wall", "-fno-rtti", "-g"}
diff --git a/rivinfo/main.cpp b/rivinfo/main.cpp
index c99e10b..d4a9b37 100644
--- a/rivinfo/main.cpp
+++ b/rivinfo/main.cpp
@@ -7,7 +7,7 @@
 #include "rive/animation/linear_animation_instance.hpp"
 #include "rive/animation/state_machine_instance.hpp"
 #include "rive/animation/state_machine_input_instance.hpp"
-#include "no_op_factory.hpp"
+#include "utils/no_op_factory.hpp"
 
 class JSoner {
     std::vector<bool> m_IsArray;
diff --git a/test/bound_bones_test.cpp b/test/bound_bones_test.cpp
index d34b330..004f8c7 100644
--- a/test/bound_bones_test.cpp
+++ b/test/bound_bones_test.cpp
@@ -7,7 +7,7 @@
 #include <rive/shapes/points_path.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_factory.hpp"
+#include "utils/no_op_factory.hpp"
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/clip_test.cpp b/test/clip_test.cpp
index 5fd4c76..6d8bada 100644
--- a/test/clip_test.cpp
+++ b/test/clip_test.cpp
@@ -3,7 +3,7 @@
 #include <rive/shapes/clipping_shape.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/default_state_machine_test.cpp b/test/default_state_machine_test.cpp
index bfe7d1a..6da2b4e 100644
--- a/test/default_state_machine_test.cpp
+++ b/test/default_state_machine_test.cpp
@@ -4,8 +4,8 @@
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
 #include <rive/animation/state_machine_instance.hpp>
-#include "no_op_factory.hpp"
-#include "no_op_renderer.hpp"
+#include "utils/no_op_factory.hpp"
+#include "utils/no_op_renderer.hpp"
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/draw_order_test.cpp b/test/draw_order_test.cpp
index 02bf3a8..be3a122 100644
--- a/test/draw_order_test.cpp
+++ b/test/draw_order_test.cpp
@@ -3,7 +3,7 @@
 #include <rive/shapes/clipping_shape.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_renderer.hpp"
+#include "utils/no_op_renderer.hpp"
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/file_test.cpp b/test/file_test.cpp
index b8cd48b..54c0fe0 100644
--- a/test/file_test.cpp
+++ b/test/file_test.cpp
@@ -2,7 +2,7 @@
 #include <rive/node.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_renderer.hpp"
+#include "utils/no_op_renderer.hpp"
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/ik_test.cpp b/test/ik_test.cpp
index ac87240..f02aef0 100644
--- a/test/ik_test.cpp
+++ b/test/ik_test.cpp
@@ -1,7 +1,7 @@
 #include <rive/node.hpp>
 #include <rive/bones/bone.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
diff --git a/test/image_asset_test.cpp b/test/image_asset_test.cpp
index 891e112..4eb4a3d 100644
--- a/test/image_asset_test.cpp
+++ b/test/image_asset_test.cpp
@@ -5,8 +5,8 @@
 #include <rive/shapes/image.hpp>
 #include <rive/assets/image_asset.hpp>
 #include <rive/relative_local_asset_resolver.hpp>
-#include "no_op_factory.hpp"
-#include "no_op_renderer.hpp"
+#include <utils/no_op_factory.hpp>
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/image_mesh_test.cpp b/test/image_mesh_test.cpp
index aa7c3b8..e04b7b2 100644
--- a/test/image_mesh_test.cpp
+++ b/test/image_mesh_test.cpp
@@ -6,7 +6,7 @@
 #include <rive/shapes/mesh.hpp>
 #include <rive/assets/image_asset.hpp>
 #include <rive/relative_local_asset_resolver.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/instancing_test.cpp b/test/instancing_test.cpp
index 93fe204..e500c6b 100644
--- a/test/instancing_test.cpp
+++ b/test/instancing_test.cpp
@@ -3,8 +3,8 @@
 #include <rive/shapes/clipping_shape.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_factory.hpp"
-#include "no_op_renderer.hpp"
+#include <utils/no_op_factory.hpp>
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/linear_animation_instance_test.cpp b/test/linear_animation_instance_test.cpp
index 469b4ae..fa24fb0 100644
--- a/test/linear_animation_instance_test.cpp
+++ b/test/linear_animation_instance_test.cpp
@@ -1,7 +1,7 @@
 #include <rive/animation/loop.hpp>
 #include <rive/animation/linear_animation.hpp>
 #include <rive/animation/linear_animation_instance.hpp>
-#include "no_op_factory.hpp"
+#include "utils/no_op_factory.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
diff --git a/test/no_op_renderer.hpp b/test/no_op_renderer.hpp
deleted file mode 100644
index 2587f86..0000000
--- a/test/no_op_renderer.hpp
+++ /dev/null
@@ -1,80 +0,0 @@
-#ifndef _RIVE_NOOP_RENDERER_HPP_
-#define _RIVE_NOOP_RENDERER_HPP_
-
-#include <rive/renderer.hpp>
-#include <rive/factory.hpp>
-#include <vector>
-
-namespace rive {
-    class NoOpRenderImage : public RenderImage {};
-
-    class NoOpRenderPaint : public RenderPaint {
-    public:
-        void color(unsigned int value) override {}
-        void style(RenderPaintStyle value) override {}
-        void thickness(float value) override {}
-        void join(StrokeJoin value) override {}
-        void cap(StrokeCap value) override {}
-        void blendMode(BlendMode value) override {}
-        void shader(rcp<RenderShader>) override {}
-    };
-
-    enum class NoOpPathCommandType { MoveTo, LineTo, CubicTo, Reset, Close };
-
-    struct NoOpPathCommand {
-        NoOpPathCommandType command;
-        float x;
-        float y;
-        float inX;
-        float inY;
-        float outX;
-        float outY;
-    };
-
-    class NoOpRenderPath : public RenderPath {
-    public:
-        std::vector<NoOpPathCommand> commands;
-        void reset() override {
-            commands.emplace_back(
-                (NoOpPathCommand){NoOpPathCommandType::Reset, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f});
-        }
-
-        void fillRule(FillRule value) override {}
-        void addPath(CommandPath* path, const Mat2D& transform) override {}
-        void addRenderPath(RenderPath* path, const Mat2D& transform) override {}
-
-        void moveTo(float x, float y) override {
-            commands.emplace_back(
-                (NoOpPathCommand){NoOpPathCommandType::MoveTo, x, y, 0.0f, 0.0f, 0.0f, 0.0f});
-        }
-        void lineTo(float x, float y) override {
-            commands.emplace_back(
-                (NoOpPathCommand){NoOpPathCommandType::LineTo, x, y, 0.0f, 0.0f, 0.0f, 0.0f});
-        }
-        void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override {
-            commands.emplace_back(
-                (NoOpPathCommand){NoOpPathCommandType::CubicTo, x, y, ix, iy, ox, oy});
-        }
-        void close() override {
-            commands.emplace_back(
-                (NoOpPathCommand){NoOpPathCommandType::Close, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f});
-        }
-    };
-
-    class NoOpRenderer : public Renderer {
-        void save() override {}
-        void restore() override {}
-        void transform(const Mat2D& transform) override {}
-        void drawPath(RenderPath* path, RenderPaint* paint) override {}
-        void clipPath(RenderPath* path) override {}
-        void drawImage(const RenderImage* image, BlendMode value, float opacity) override {}
-        void drawImageMesh(const RenderImage*,
-                           rcp<RenderBuffer> vertices,
-                           rcp<RenderBuffer> uvCoords,
-                           rcp<RenderBuffer> indices,
-                           BlendMode,
-                           float opacity) override {}
-    };
-
-} // namespace rive
-#endif
diff --git a/test/path_test.cpp b/test/path_test.cpp
index fb0e5fb..931e0c3 100644
--- a/test/path_test.cpp
+++ b/test/path_test.cpp
@@ -6,13 +6,66 @@
 #include <rive/shapes/path_composer.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_factory.hpp"
-#include "no_op_renderer.hpp"
+#include <utils/no_op_factory.hpp>
+#include <utils/no_op_renderer.hpp>
 #include <catch.hpp>
 #include <cstdio>
 
+// Need a specialized "noop" factory that does make an inspectable Path
+
+namespace {
+    enum class TestPathCommandType { MoveTo, LineTo, CubicTo, Reset, Close };
+
+    struct TestPathCommand {
+        TestPathCommandType command;
+        float x;
+        float y;
+        float inX;
+        float inY;
+        float outX;
+        float outY;
+    };
+
+    class TestRenderPath : public rive::RenderPath {
+    public:
+        std::vector<TestPathCommand> commands;
+        void reset() override {
+            commands.emplace_back(
+                (TestPathCommand){TestPathCommandType::Reset, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f});
+        }
+
+        void fillRule(rive::FillRule value) override {}
+        void addPath(rive::CommandPath* path, const rive::Mat2D& transform) override {}
+        void addRenderPath(rive::RenderPath* path, const rive::Mat2D& transform) override {}
+
+        void moveTo(float x, float y) override {
+            commands.emplace_back(
+                (TestPathCommand){TestPathCommandType::MoveTo, x, y, 0.0f, 0.0f, 0.0f, 0.0f});
+        }
+        void lineTo(float x, float y) override {
+            commands.emplace_back(
+                (TestPathCommand){TestPathCommandType::LineTo, x, y, 0.0f, 0.0f, 0.0f, 0.0f});
+        }
+        void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override {
+            commands.emplace_back(
+                (TestPathCommand){TestPathCommandType::CubicTo, x, y, ix, iy, ox, oy});
+        }
+        void close() override {
+            commands.emplace_back(
+                (TestPathCommand){TestPathCommandType::Close, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f});
+        }
+    };
+
+    class TestNoOpFactory : public rive::NoOpFactory {
+    public:
+        std::unique_ptr<rive::RenderPath> makeEmptyRenderPath() override {
+            return std::make_unique<TestRenderPath>();
+        }
+    };
+} // namespace
+
 TEST_CASE("rectangle path builds expected commands", "[path]") {
-    rive::NoOpFactory emptyFactory;
+    TestNoOpFactory emptyFactory;
     rive::Artboard artboard(&emptyFactory);
     rive::Shape* shape = new rive::Shape();
     rive::Rectangle* rectangle = new rive::Rectangle();
@@ -34,20 +87,20 @@
 
     REQUIRE(rectangle->commandPath() != nullptr);
 
-    auto path = reinterpret_cast<rive::NoOpRenderPath*>(rectangle->commandPath());
+    auto path = reinterpret_cast<TestRenderPath*>(rectangle->commandPath());
 
     REQUIRE(path->commands.size() == 7);
-    REQUIRE(path->commands[0].command == rive::NoOpPathCommandType::Reset);
-    REQUIRE(path->commands[1].command == rive::NoOpPathCommandType::MoveTo);
-    REQUIRE(path->commands[2].command == rive::NoOpPathCommandType::LineTo);
-    REQUIRE(path->commands[3].command == rive::NoOpPathCommandType::LineTo);
-    REQUIRE(path->commands[4].command == rive::NoOpPathCommandType::LineTo);
-    REQUIRE(path->commands[5].command == rive::NoOpPathCommandType::LineTo);
-    REQUIRE(path->commands[6].command == rive::NoOpPathCommandType::Close);
+    REQUIRE(path->commands[0].command == TestPathCommandType::Reset);
+    REQUIRE(path->commands[1].command == TestPathCommandType::MoveTo);
+    REQUIRE(path->commands[2].command == TestPathCommandType::LineTo);
+    REQUIRE(path->commands[3].command == TestPathCommandType::LineTo);
+    REQUIRE(path->commands[4].command == TestPathCommandType::LineTo);
+    REQUIRE(path->commands[5].command == TestPathCommandType::LineTo);
+    REQUIRE(path->commands[6].command == TestPathCommandType::Close);
 }
 
 TEST_CASE("rounded rectangle path builds expected commands", "[path]") {
-    rive::NoOpFactory emptyFactory;
+    TestNoOpFactory emptyFactory;
     rive::Artboard artboard(&emptyFactory);
     rive::Shape* shape = new rive::Shape();
     rive::Rectangle* rectangle = new rive::Rectangle();
@@ -70,7 +123,7 @@
 
     REQUIRE(rectangle->commandPath() != nullptr);
 
-    auto path = reinterpret_cast<rive::NoOpRenderPath*>(rectangle->commandPath());
+    auto path = reinterpret_cast<TestRenderPath*>(rectangle->commandPath());
 
     // reset
     // moveTo
@@ -85,31 +138,31 @@
     REQUIRE(path->commands.size() == 11);
 
     // Init
-    REQUIRE(path->commands[0].command == rive::NoOpPathCommandType::Reset);
-    REQUIRE(path->commands[1].command == rive::NoOpPathCommandType::MoveTo);
+    REQUIRE(path->commands[0].command == TestPathCommandType::Reset);
+    REQUIRE(path->commands[1].command == TestPathCommandType::MoveTo);
 
     // 1st
-    REQUIRE(path->commands[2].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[2].command == TestPathCommandType::CubicTo);
 
     // 2nd
-    REQUIRE(path->commands[3].command == rive::NoOpPathCommandType::LineTo);
-    REQUIRE(path->commands[4].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[3].command == TestPathCommandType::LineTo);
+    REQUIRE(path->commands[4].command == TestPathCommandType::CubicTo);
 
     // 3rd
-    REQUIRE(path->commands[5].command == rive::NoOpPathCommandType::LineTo);
-    REQUIRE(path->commands[6].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[5].command == TestPathCommandType::LineTo);
+    REQUIRE(path->commands[6].command == TestPathCommandType::CubicTo);
 
     // 4th
-    REQUIRE(path->commands[7].command == rive::NoOpPathCommandType::LineTo);
-    REQUIRE(path->commands[8].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[7].command == TestPathCommandType::LineTo);
+    REQUIRE(path->commands[8].command == TestPathCommandType::CubicTo);
 
-    REQUIRE(path->commands[9].command == rive::NoOpPathCommandType::LineTo);
+    REQUIRE(path->commands[9].command == TestPathCommandType::LineTo);
 
-    REQUIRE(path->commands[10].command == rive::NoOpPathCommandType::Close);
+    REQUIRE(path->commands[10].command == TestPathCommandType::Close);
 }
 
 TEST_CASE("ellipse path builds expected commands", "[path]") {
-    rive::NoOpFactory emptyFactory;
+    TestNoOpFactory emptyFactory;
     rive::Artboard artboard(&emptyFactory);
     rive::Ellipse* ellipse = new rive::Ellipse();
     rive::Shape* shape = new rive::Shape();
@@ -130,7 +183,7 @@
 
     REQUIRE(ellipse->commandPath() != nullptr);
 
-    auto path = reinterpret_cast<rive::NoOpRenderPath*>(ellipse->commandPath());
+    auto path = reinterpret_cast<TestRenderPath*>(ellipse->commandPath());
 
     // reset
     // moveTo
@@ -145,13 +198,13 @@
     REQUIRE(path->commands.size() == 7);
 
     // Init
-    REQUIRE(path->commands[0].command == rive::NoOpPathCommandType::Reset);
-    REQUIRE(path->commands[1].command == rive::NoOpPathCommandType::MoveTo);
+    REQUIRE(path->commands[0].command == TestPathCommandType::Reset);
+    REQUIRE(path->commands[1].command == TestPathCommandType::MoveTo);
     REQUIRE(path->commands[1].x == 0.0f);
     REQUIRE(path->commands[1].y == -100.0f);
 
     // 1st
-    REQUIRE(path->commands[2].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[2].command == TestPathCommandType::CubicTo);
     REQUIRE(path->commands[2].outX == 50.0f * rive::circleConstant);
     REQUIRE(path->commands[2].outY == -100.0f);
     REQUIRE(path->commands[2].inX == 50.0f);
@@ -160,7 +213,7 @@
     REQUIRE(path->commands[2].y == 0.0f);
 
     // 2nd
-    REQUIRE(path->commands[3].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[3].command == TestPathCommandType::CubicTo);
     REQUIRE(path->commands[3].outX == 50.0f);
     REQUIRE(path->commands[3].outY == 100.0f * rive::circleConstant);
     REQUIRE(path->commands[3].inX == 50.0f * rive::circleConstant);
@@ -169,7 +222,7 @@
     REQUIRE(path->commands[3].y == 100.0f);
 
     // 3rd
-    REQUIRE(path->commands[4].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[4].command == TestPathCommandType::CubicTo);
     REQUIRE(path->commands[4].outX == -50.0f * rive::circleConstant);
     REQUIRE(path->commands[4].outY == 100.0f);
     REQUIRE(path->commands[4].inX == -50.0f);
@@ -178,7 +231,7 @@
     REQUIRE(path->commands[4].y == 0.0f);
 
     // 4th
-    REQUIRE(path->commands[5].command == rive::NoOpPathCommandType::CubicTo);
+    REQUIRE(path->commands[5].command == TestPathCommandType::CubicTo);
     REQUIRE(path->commands[5].outX == -50.0f);
     REQUIRE(path->commands[5].outY == -100.0f * rive::circleConstant);
     REQUIRE(path->commands[5].inX == -50.0f * rive::circleConstant);
@@ -186,5 +239,5 @@
     REQUIRE(path->commands[5].x == 0.0f);
     REQUIRE(path->commands[5].y == -100.0f);
 
-    REQUIRE(path->commands[6].command == rive::NoOpPathCommandType::Close);
+    REQUIRE(path->commands[6].command == TestPathCommandType::Close);
 }
\ No newline at end of file
diff --git a/test/raw_path_test.cpp b/test/raw_path_test.cpp
index b2a0495..8b34f66 100644
--- a/test/raw_path_test.cpp
+++ b/test/raw_path_test.cpp
@@ -4,7 +4,6 @@
 
 #include <rive/math/aabb.hpp>
 #include <rive/math/raw_path.hpp>
-#include "no_op_renderer.hpp"
 
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/rive_file_reader.hpp b/test/rive_file_reader.hpp
index 64a36cb..7fb4549 100644
--- a/test/rive_file_reader.hpp
+++ b/test/rive_file_reader.hpp
@@ -5,14 +5,16 @@
 #include "rive/rive_counter.hpp"
 
 #include "rive_testing.hpp"
-#include "no_op_factory.hpp"
+#include "utils/no_op_factory.hpp"
+
+static rive::NoOpFactory gNoOpFactory;
 
 static inline std::unique_ptr<rive::File>
 ReadRiveFile(const char path[],
              rive::Factory* factory = nullptr,
              rive::FileAssetResolver* resolver = nullptr) {
     if (!factory) {
-        factory = &rive::gNoOpFactory;
+        factory = &gNoOpFactory;
     }
 
     FILE* fp = fopen(path, "rb");
diff --git a/test/rotation_constraint_test.cpp b/test/rotation_constraint_test.cpp
index 974e284..5419e87 100644
--- a/test/rotation_constraint_test.cpp
+++ b/test/rotation_constraint_test.cpp
@@ -3,7 +3,7 @@
 #include <rive/bones/bone.hpp>
 #include <rive/shapes/shape.hpp>
 #include <rive/math/transform_components.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
diff --git a/test/scale_constraint_test.cpp b/test/scale_constraint_test.cpp
index edf96f5..efe76ac 100644
--- a/test/scale_constraint_test.cpp
+++ b/test/scale_constraint_test.cpp
@@ -3,7 +3,7 @@
 #include <rive/bones/bone.hpp>
 #include <rive/shapes/shape.hpp>
 #include <rive/math/transform_components.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
diff --git a/test/stroke_test.cpp b/test/stroke_test.cpp
index 3af2791..ed47ee1 100644
--- a/test/stroke_test.cpp
+++ b/test/stroke_test.cpp
@@ -5,7 +5,7 @@
 #include <rive/shapes/paint/stroke.hpp>
 #include <rive/shapes/paint/solid_color.hpp>
 #include <rive/shapes/paint/color.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
diff --git a/test/transform_constraint_test.cpp b/test/transform_constraint_test.cpp
index 12707ba..64f1f75 100644
--- a/test/transform_constraint_test.cpp
+++ b/test/transform_constraint_test.cpp
@@ -2,7 +2,7 @@
 #include <rive/node.hpp>
 #include <rive/bones/bone.hpp>
 #include <rive/shapes/shape.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
diff --git a/test/translation_constraint_test.cpp b/test/translation_constraint_test.cpp
index e8a0eb8..5452019 100644
--- a/test/translation_constraint_test.cpp
+++ b/test/translation_constraint_test.cpp
@@ -3,7 +3,7 @@
 #include <rive/bones/bone.hpp>
 #include <rive/shapes/shape.hpp>
 #include <rive/math/transform_components.hpp>
-#include "no_op_renderer.hpp"
+#include <utils/no_op_renderer.hpp>
 #include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
diff --git a/test/no_op_factory.cpp b/utils/no_op_factory.cpp
similarity index 61%
rename from test/no_op_factory.cpp
rename to utils/no_op_factory.cpp
index dc8703c..c421e08 100644
--- a/test/no_op_factory.cpp
+++ b/utils/no_op_factory.cpp
@@ -1,9 +1,38 @@
-#include "no_op_factory.hpp"
-#include "no_op_renderer.hpp"
+#include "utils/no_op_factory.hpp"
+#include "utils/no_op_renderer.hpp"
 
 using namespace rive;
 
-NoOpFactory gNoOpFactory;
+namespace {
+    class NoOpRenderImage : public RenderImage {
+    public:
+    };
+
+    class NoOpRenderPaint : public RenderPaint {
+    public:
+        void color(unsigned int value) override {}
+        void style(RenderPaintStyle value) override {}
+        void thickness(float value) override {}
+        void join(StrokeJoin value) override {}
+        void cap(StrokeCap value) override {}
+        void blendMode(BlendMode value) override {}
+        void shader(rcp<RenderShader>) override {}
+    };
+
+    class NoOpRenderPath : public RenderPath {
+    public:
+        void reset() override {}
+
+        void fillRule(FillRule value) override {}
+        void addPath(CommandPath* path, const Mat2D& transform) override {}
+        void addRenderPath(RenderPath* path, const Mat2D& transform) override {}
+
+        void moveTo(float x, float y) override {}
+        void lineTo(float x, float y) override {}
+        void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override {}
+        void close() override {}
+    };
+} // namespace
 
 rcp<RenderBuffer> NoOpFactory::makeBufferU16(Span<const uint16_t>) { return nullptr; }
 rcp<RenderBuffer> NoOpFactory::makeBufferU32(Span<const uint32_t>) { return nullptr; }