diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 094efbd440a452bcc7a323299f39eb52dcd3046b..e5c35aab5062d7da7a1be90159d8440b2a6f8370 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -69,6 +69,7 @@ add_executable (unit_tests
   test_ListAffectationProcessor.cpp
   test_NameProcessor.cpp
   test_OStreamProcessor.cpp
+  test_ParseError.cpp
   test_PCG.cpp
   test_PugsFunctionAdapter.cpp
   test_PugsAssert.cpp
diff --git a/tests/test_ParseError.cpp b/tests/test_ParseError.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0fb8eb5dca61f1098592eafd1d84d51f2e953bc0
--- /dev/null
+++ b/tests/test_ParseError.cpp
@@ -0,0 +1,39 @@
+#include <catch2/catch.hpp>
+
+#include <language/utils/ParseError.hpp>
+
+#include <string>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ParseError", "[language]")
+{
+  SECTION("single position")
+  {
+    const std::string source = R"(
+a first line
+a second line
+)";
+    TAO_PEGTL_NAMESPACE::internal::iterator i(&source[0], 3, 1, 2);
+    TAO_PEGTL_NAMESPACE::position p{i, source};
+    ParseError parse_error("error message", p);
+    REQUIRE(parse_error.positions() == std::vector{p});
+    REQUIRE(parse_error.what() == std::string{"error message"});
+  }
+
+  SECTION("position list")
+  {
+    const std::string source = R"(
+a first line
+a second line
+)";
+    TAO_PEGTL_NAMESPACE::internal::iterator i0(&source[0], 3, 1, 2);
+    TAO_PEGTL_NAMESPACE::position p0{i0, source};
+    TAO_PEGTL_NAMESPACE::internal::iterator i1(&source[0], 4, 1, 3);
+    TAO_PEGTL_NAMESPACE::position p1{i1, source};
+
+    ParseError parse_error("error message", std::vector{p0, p1});
+    REQUIRE(parse_error.positions() == std::vector{p0, p1});
+    REQUIRE(parse_error.what() == std::string{"error message"});
+  }
+}