update more tests
diff --git a/test/bound_bones_test.cpp b/test/bound_bones_test.cpp
index 3aa01e1..cb647c0 100644
--- a/test/bound_bones_test.cpp
+++ b/test/bound_bones_test.cpp
@@ -9,29 +9,14 @@
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("bound bones load correctly", "[bones]") {
-    FILE* fp = fopen("../../test/assets/off_road_car.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/off_road_car.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto node = file->artboard()->find("transmission_front_testing");
+    auto node = reader.file()->artboard()->find("transmission_front_testing");
     REQUIRE(node != nullptr);
     REQUIRE(node->is<rive::Shape>());
     REQUIRE(node->as<rive::Shape>()->paths().size() == 1);
@@ -48,7 +33,4 @@
     }
 
     // Ok seems like bones are set up ok.
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/clip_test.cpp b/test/clip_test.cpp
index 1b60766..8a0cdf3 100644
--- a/test/clip_test.cpp
+++ b/test/clip_test.cpp
@@ -5,26 +5,14 @@
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("clipping loads correctly", "[clipping]") {
-    FILE* fp = fopen("../../test/assets/circle_clips.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/circle_clips.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
+    auto file = reader.file();
     auto node = file->artboard()->find("TopEllipse");
     REQUIRE(node != nullptr);
     REQUIRE(node->is<rive::Shape>());
@@ -38,7 +26,4 @@
 
     rive::NoOpRenderer renderer;
     file->artboard()->draw(&renderer);
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/distance_constraint_test.cpp b/test/distance_constraint_test.cpp
index 529198a..e70ce88 100644
--- a/test/distance_constraint_test.cpp
+++ b/test/distance_constraint_test.cpp
@@ -4,27 +4,14 @@
 #include <rive/node.hpp>
 #include <rive/math/vec2d.hpp>
 #include <rive/shapes/shape.hpp>
+#include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
 
 TEST_CASE("distance constraints moves items as expected", "[file]") {
-    FILE* fp = fopen("../../test/assets/distance_constraint.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/distance_constraint.riv");
 
-    fseek(fp, 0, SEEK_END);
-    const size_t length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
 
     REQUIRE(artboard->find<rive::Shape>("A") != nullptr);
     auto a = artboard->find<rive::Shape>("A");
@@ -47,7 +34,4 @@
     a->worldTranslation(at);
     rive::Vec2D expectedTranslation(259.2808837890625, 62.87000274658203);
     REQUIRE(rive::Vec2D::distance(at, expectedTranslation) < 0.001f);
-
-    delete file;
-    delete[] bytes;
 }
diff --git a/test/draw_order_test.cpp b/test/draw_order_test.cpp
index 1544e38..32e0b9d 100644
--- a/test/draw_order_test.cpp
+++ b/test/draw_order_test.cpp
@@ -5,26 +5,14 @@
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("draw rules load and sort correctly", "[draw rules]") {
-    FILE* fp = fopen("../../test/assets/draw_rule_cycle.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/draw_rule_cycle.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
+    // auto file = reader.file();
     // auto node = file->artboard()->node("TopEllipse");
     // REQUIRE(node != nullptr);
     // REQUIRE(node->is<rive::Shape>());
@@ -38,6 +26,4 @@
 
     // rive::NoOpRenderer renderer;
     // file->artboard()->draw(&renderer);
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/ik_constraint_test.cpp b/test/ik_constraint_test.cpp
index c4beb4b..1481171 100644
--- a/test/ik_constraint_test.cpp
+++ b/test/ik_constraint_test.cpp
@@ -6,27 +6,14 @@
 #include <rive/shapes/shape.hpp>
 #include <rive/bones/skin.hpp>
 #include <rive/bones/bone.hpp>
+#include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
 
 TEST_CASE("ik with skinned bones orders correctly", "[file]") {
-    FILE* fp = fopen("../../test/assets/complex_ik_dependency.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/complex_ik_dependency.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
 
     REQUIRE(artboard->find<rive::Bone>("One") != nullptr);
     auto one = artboard->find<rive::Bone>("One");
@@ -46,7 +33,4 @@
 
     REQUIRE(skin->graphOrder() > one->graphOrder());
     REQUIRE(skin->graphOrder() > two->graphOrder());
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/image_asset_test.cpp b/test/image_asset_test.cpp
index 8d1f979..b037089 100644
--- a/test/image_asset_test.cpp
+++ b/test/image_asset_test.cpp
@@ -7,25 +7,13 @@
 #include <rive/assets/image_asset.hpp>
 #include <rive/relative_local_asset_resolver.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("image assets loads correctly", "[assets]") {
-    FILE* fp = fopen("../../test/assets/walle.riv", "r");
-    REQUIRE(fp != nullptr);
-
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
+    RiveFileReader reader("../../test/assets/walle.riv");
+    auto file = reader.file();
 
     auto node = file->artboard()->find("walle");
     REQUIRE(node != nullptr);
@@ -53,9 +41,6 @@
 
     rive::NoOpRenderer renderer;
     file->artboard()->draw(&renderer);
-
-    delete file;
-    delete[] bytes;
 }
 
 TEST_CASE("out of band image assets loads correctly", "[assets]") {
@@ -107,4 +92,4 @@
 
     delete file;
     delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/rotation_constraint_test.cpp b/test/rotation_constraint_test.cpp
index 7398795..30a62e6 100644
--- a/test/rotation_constraint_test.cpp
+++ b/test/rotation_constraint_test.cpp
@@ -5,27 +5,14 @@
 #include <rive/shapes/shape.hpp>
 #include <rive/math/transform_components.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
 
 TEST_CASE("rotation constraint updates world transform", "[file]") {
-    FILE* fp = fopen("../../test/assets/rotation_constraint.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/rotation_constraint.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("target");
@@ -40,7 +27,4 @@
     rive::Mat2D::decompose(rectComponents, rectangle->worldTransform());
 
     REQUIRE(targetComponents.rotation() == rectComponents.rotation());
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/scale_constraint_test.cpp b/test/scale_constraint_test.cpp
index c6e724a..9770fc0 100644
--- a/test/scale_constraint_test.cpp
+++ b/test/scale_constraint_test.cpp
@@ -5,27 +5,14 @@
 #include <rive/shapes/shape.hpp>
 #include <rive/math/transform_components.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
 
 TEST_CASE("scale constraint updates world transform", "[file]") {
-    FILE* fp = fopen("../../test/assets/scale_constraint.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/scale_constraint.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("target");
@@ -42,7 +29,4 @@
 
     REQUIRE(targetComponents.scaleX() == rectComponents.scaleX());
     REQUIRE(targetComponents.scaleY() == rectComponents.scaleY());
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/state_machine_test.cpp b/test/state_machine_test.cpp
index 6bab974..6e23dc1 100644
--- a/test/state_machine_test.cpp
+++ b/test/state_machine_test.cpp
@@ -12,24 +12,13 @@
 #include <rive/animation/blend_state_direct.hpp>
 #include <rive/animation/blend_state_transition.hpp>
 #include "catch.hpp"
+#include "rive_file_reader.hpp"
 #include <cstdio>
 
 TEST_CASE("file with state machine be read", "[file]") {
-    FILE* fp = fopen("../../test/assets/rocket.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/rocket.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
     REQUIRE(artboard != nullptr);
     REQUIRE(artboard->animationCount() == 3);
     REQUIRE(artboard->stateMachineCount() == 1);
@@ -94,27 +83,12 @@
     REQUIRE(smi.getBool("Press") != nullptr);
     REQUIRE(smi.stateChangedCount() == 0);
     REQUIRE(smi.currentAnimationCount() == 0);
-
-    delete file;
-    delete[] bytes;
 }
 
 TEST_CASE("file with blend states loads correctly", "[file]") {
-    FILE* fp = fopen("../../test/assets/blend_test.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/blend_test.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
     REQUIRE(artboard != nullptr);
     REQUIRE(artboard->animationCount() == 4);
     REQUIRE(artboard->stateMachineCount() == 2);
@@ -165,27 +139,12 @@
     REQUIRE(blendStateA->transition(0)
                 ->as<rive::BlendStateTransition>()
                 ->exitBlendAnimation() != nullptr);
-
-    delete file;
-    delete[] bytes;
 }
 
 TEST_CASE("animation state with no animation doesn't crash", "[file]") {
-    FILE* fp = fopen("../../test/assets/multiple_state_machines.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/multiple_state_machines.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
     REQUIRE(artboard != nullptr);
     REQUIRE(artboard->animationCount() == 1);
     REQUIRE(artboard->stateMachineCount() == 4);
@@ -208,7 +167,4 @@
 
     rive::StateMachineInstance smi(stateMachine);
     smi.advance(artboard, 0.0f);
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/stroke_test.cpp b/test/stroke_test.cpp
index 751a261..0359a69 100644
--- a/test/stroke_test.cpp
+++ b/test/stroke_test.cpp
@@ -7,33 +7,17 @@
 #include <rive/shapes/paint/solid_color.hpp>
 #include <rive/shapes/paint/color.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("stroke can be looked up at runtime", "[file]") {
-    FILE* fp = fopen("../../test/assets/stroke_name_test.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/stroke_name_test.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
     REQUIRE(artboard->find<rive::Stroke>("white_stroke") != nullptr);
     auto stroke = artboard->find<rive::Stroke>("white_stroke");
     REQUIRE(stroke->paint()->is<rive::SolidColor>());
     stroke->paint()->as<rive::SolidColor>()->colorValue(
         rive::colorARGB(255, 0, 255, 255));
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/transform_constraint_test.cpp b/test/transform_constraint_test.cpp
index b47c278..b32173b 100644
--- a/test/transform_constraint_test.cpp
+++ b/test/transform_constraint_test.cpp
@@ -4,27 +4,14 @@
 #include <rive/bones/bone.hpp>
 #include <rive/shapes/shape.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
 
 TEST_CASE("transform constraint updates world transform", "[file]") {
-    FILE* fp = fopen("../../test/assets/transform_constraint.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/transform_constraint.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("Target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("Target");
@@ -37,7 +24,4 @@
     // Expect the transform constraint to have placed the shape in the same
     // exact world transform as the target.
     REQUIRE(aboutEqual(target->worldTransform(), rectangle->worldTransform()));
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}
diff --git a/test/translation_constraint_test.cpp b/test/translation_constraint_test.cpp
index feb4bf7..5e06bb5 100644
--- a/test/translation_constraint_test.cpp
+++ b/test/translation_constraint_test.cpp
@@ -5,27 +5,14 @@
 #include <rive/shapes/shape.hpp>
 #include <rive/math/transform_components.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
 #include <cstdio>
 
 TEST_CASE("translation constraint updates world transform", "[file]") {
-    FILE* fp = fopen("../../test/assets/translation_constraint.riv", "r");
-    REQUIRE(fp != nullptr);
+    RiveFileReader reader("../../test/assets/translation_constraint.riv");
 
-    fseek(fp, 0, SEEK_END);
-    auto length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    rive::File* file = nullptr;
-    auto result = rive::File::import(reader, &file);
-
-    REQUIRE(result == rive::ImportResult::success);
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
-
-    auto artboard = file->artboard();
+    auto artboard = reader.file()->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("target");
@@ -42,7 +29,4 @@
 
     REQUIRE(targetComponents.x() == rectComponents.x());
     REQUIRE(targetComponents.y() == rectComponents.y());
-
-    delete file;
-    delete[] bytes;
-}
\ No newline at end of file
+}