diff --git a/test/TestUriRelative.cxx b/test/TestUriRelative.cxx
new file mode 100644
index 000000000..762a1e04e
--- /dev/null
+++ b/test/TestUriRelative.cxx
@@ -0,0 +1,52 @@
+/*
+ * Unit tests for src/util/UriRelative.hxx
+ */
+
+#include "util/UriRelative.hxx"
+
+#include <gtest/gtest.h>
+
+TEST(UriRelative, IsChild)
+{
+	static constexpr struct {
+		const char *parent;
+		const char *child;
+		bool is_child;
+		bool is_child_or_same;
+	} tests[] = {
+		{ "/foo", "/foo", false, true },
+		{ "/foo", "/foo/bar", true, true },
+		{ "/foo/", "/foo/bar", true, true },
+		{ "/foo/", "/foo/", false, true },
+		{ "/foo/", "/foo", false, false },
+		{ "/bar", "/foo", false, false },
+		{ "/foo", "/foobar", false, false },
+	};
+
+	for (const auto &i : tests) {
+		EXPECT_EQ(uri_is_child(i.parent, i.child), i.is_child);
+		EXPECT_EQ(uri_is_child_or_same(i.parent, i.child),
+			  i.is_child_or_same);
+	}
+}
+
+TEST(UriRelative, ApplyBase)
+{
+	static constexpr struct {
+		const char *uri;
+		const char *base;
+		const char *result;
+	} tests[] = {
+		{ "foo", "bar", "bar/foo" },
+		{ "foo", "/bar", "/bar/foo" },
+		{ "/foo", "/bar", "/foo" },
+		{ "/foo", "bar", "/foo" },
+		{ "/foo", "http://localhost/bar", "http://localhost/foo" },
+		{ "/foo", "http://localhost/", "http://localhost/foo" },
+		{ "/foo", "http://localhost", "http://localhost/foo" },
+	};
+
+	for (const auto &i : tests) {
+		EXPECT_STREQ(uri_apply_base(i.uri, i.base).c_str(), i.result);
+	}
+}
diff --git a/test/meson.build b/test/meson.build
index 0d89724b9..bdd6d82c7 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -39,6 +39,7 @@ test('TestUtil', executable(
   'TestSplitString.cxx',
   'TestUriExtract.cxx',
   'TestUriQueryParser.cxx',
+  'TestUriRelative.cxx',
   'TestUriUtil.cxx',
   'test_byte_reverse.cxx',
   include_directories: inc,