diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6184264ebe676d2ff51e6bb47de45fb7d3d8aa47..0c0392a04e453435d854f669743ca3701fd010eb 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -6,7 +6,8 @@ find_package(Boost 1.45.0 REQUIRED COMPONENTS unit_test_framework)
 include_directories(${Boost_INCLUDE_DIRS})
 
 foreach(TEST
-        write_test)
+        write_test
+        skinpart_test)
     add_executable(${TEST} ${TEST}.cpp)
     target_link_libraries(${TEST} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} niflib)
     add_test(niflib::${TEST} ${TEST})
diff --git a/test/skinpart_test.cpp b/test/skinpart_test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2128b05fb83e3327abaf47583b95c20d4215a5bc
--- /dev/null
+++ b/test/skinpart_test.cpp
@@ -0,0 +1,97 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+#include <boost/test/unit_test.hpp>
+
+#include <sstream> // stringstream
+
+// evil hack to allow testing of private and protected data
+#define private public
+#define protected public 
+
+#include "niflib.h"
+#include "obj/NiNode.h"
+#include "obj/NiSkinInstance.h"
+#include "obj/NiSkinPartition.h"
+#include "obj/NiTriStrips.h"
+#include "obj/NiTriStripsData.h"
+
+using namespace Niflib;
+using namespace std;
+
+BOOST_AUTO_TEST_SUITE(skinpart_test_suite)
+
+BOOST_AUTO_TEST_CASE(write_skinpart_test)
+{
+  stringstream ss;
+  stringstream ss2;
+
+  // create a simple nif tree with a skin partition
+  NiNodeRef root = new NiNode;
+  NiNodeRef bone = new NiNode;
+  NiTriStripsRef shape = new NiTriStrips;
+  NiTriStripsDataRef data = new NiTriStripsData;
+  // set hierarchy
+  shape->SetData(data);
+  root->AddChild(DynamicCast<NiAVObject>(shape));
+  root->AddChild(DynamicCast<NiAVObject>(bone));
+  // add a triangle to the geometry
+  {
+    vector<Vector3> verts;
+    vector<Triangle> tris;
+    verts.push_back(Vector3(0, 0, 0));
+    verts.push_back(Vector3(0, 1, 0));
+    verts.push_back(Vector3(0, 0, 1));
+    tris.push_back(Triangle(0, 1, 2));
+    data->SetVertices(verts);
+    data->SetTriangles(tris);
+  }
+  // bind skin to bone
+  {
+    vector<NiNodeRef> bones;
+    bones.push_back(bone);
+    shape->BindSkin(bones);
+    vector<SkinWeight> weights;
+    SkinWeight sw;
+    sw.weight = 1;
+    sw.index = 0;
+    weights.push_back(sw);
+    sw.index = 1;
+    weights.push_back(sw);
+    sw.index = 2;
+    weights.push_back(sw);
+    shape->SetBoneWeights(0, weights);
+    shape->GenHardwareSkinInfo();
+  }
+  // check number of triangles
+  NiSkinInstanceRef skininst = shape->GetSkinInstance();
+  BOOST_REQUIRE(skininst != NULL);
+  NiSkinPartitionRef skinpart = skininst->GetSkinPartition();
+  BOOST_REQUIRE(skinpart != NULL);
+  BOOST_REQUIRE_EQUAL(skinpart->GetNumPartitions(), 1);
+  BOOST_CHECK_EQUAL(skinpart->skinPartitionBlocks[0].numTriangles, 1);
+  // write, read, write, read, and check that numTriangles is still 1
+  // (the first write checks that GenHardwareSkinInfo sets the
+  // triangle list; the second write checks that this works even
+  // without GenHardwareSkinInfo)
+  NiObjectRef obj;
+  BOOST_CHECK_NO_THROW(WriteNifTree(ss, root, NifInfo(VER_20_0_0_5)));
+  ss.seekg(0);
+  BOOST_CHECK_NO_THROW(obj = ReadNifTree(ss));
+  ss2.seekg(0);
+  BOOST_CHECK_NO_THROW(WriteNifTree(ss2, obj, NifInfo(VER_20_0_0_5)));
+  ss2.seekg(0);
+  BOOST_CHECK_NO_THROW(obj = ReadNifTree(ss2));
+  root = DynamicCast<NiNode>(obj);
+  BOOST_REQUIRE(root != NULL);
+  BOOST_REQUIRE_EQUAL(root->GetChildren().size(), 2);
+  shape = DynamicCast<NiTriStrips>(root->GetChildren()[0]);
+  BOOST_REQUIRE(shape != NULL);
+  skininst = shape->GetSkinInstance();
+  BOOST_REQUIRE(skininst != NULL);
+  skinpart = skininst->GetSkinPartition();
+  BOOST_REQUIRE(skinpart != NULL);
+  BOOST_REQUIRE_EQUAL(skinpart->GetNumPartitions(), 1);
+  BOOST_CHECK_EQUAL(skinpart->skinPartitionBlocks[0].numTriangles, 1);
+}
+
+BOOST_AUTO_TEST_SUITE_END()