diff --git a/lib/common/tests/strings/Makefile.am b/lib/common/tests/strings/Makefile.am
index 13a81beb2d..e6b0fabd7b 100644
--- a/lib/common/tests/strings/Makefile.am
+++ b/lib/common/tests/strings/Makefile.am
@@ -1,31 +1,32 @@
 #
-# Copyright 2020-2021 the Pacemaker project contributors
+# Copyright 2020-2022 the Pacemaker project contributors
 #
 # The version control history for this file may have further details.
 #
 # This source code is licensed under the GNU General Public License version 2
 # or later (GPLv2+) WITHOUT ANY WARRANTY.
 #
 
 AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
 LDADD = $(top_builddir)/lib/common/libcrmcommon.la -lcmocka
 
 include $(top_srcdir)/mk/tap.mk
 
 # Add "_test" to the end of all test program names to simplify .gitignore.
 check_PROGRAMS = \
 		crm_get_msec_test 	\
 		crm_is_true_test 	\
 		crm_str_to_boolean_test 	\
 		pcmk__add_word_test		\
 		pcmk__btoa_test			\
 		pcmk__char_in_any_str_test	\
 		pcmk__ends_with_test 		\
 		pcmk__parse_ll_range_test	\
 		pcmk__scan_double_test		\
+		pcmk__scan_port_test		\
 		pcmk__starts_with_test 		\
 		pcmk__str_any_of_test		\
 		pcmk__str_in_list_test		\
 		pcmk__strcmp_test
 
 TESTS = $(check_PROGRAMS)
diff --git a/lib/common/tests/strings/pcmk__scan_port_test.c b/lib/common/tests/strings/pcmk__scan_port_test.c
new file mode 100644
index 0000000000..0a6a1a96d2
--- /dev/null
+++ b/lib/common/tests/strings/pcmk__scan_port_test.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+static void
+empty_input_string(void **state)
+{
+    int result;
+
+    assert_int_equal(pcmk__scan_port("", &result), EINVAL);
+    assert_int_equal(result, -1);
+}
+
+static void
+bad_input_string(void **state)
+{
+    int result;
+
+    assert_int_equal(pcmk__scan_port("abc", &result), EINVAL);
+    assert_int_equal(result, -1);
+}
+
+static void
+out_of_range(void **state)
+{
+    int result;
+
+    assert_int_equal(pcmk__scan_port("-1", &result), pcmk_rc_before_range);
+    assert_int_equal(result, -1);
+    assert_int_equal(pcmk__scan_port("65536",  &result), pcmk_rc_after_range);
+    assert_int_equal(result, -1);
+}
+
+static void
+typical_case(void **state)
+{
+    int result;
+
+    assert_int_equal(pcmk__scan_port("0", &result), pcmk_rc_ok);
+    assert_int_equal(result, 0);
+
+    assert_int_equal(pcmk__scan_port("80", &result), pcmk_rc_ok);
+    assert_int_equal(result, 80);
+}
+
+int main(int argc, char **argv)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test(empty_input_string),
+        cmocka_unit_test(bad_input_string),
+        cmocka_unit_test(out_of_range),
+        cmocka_unit_test(typical_case),
+    };
+
+    cmocka_set_message_output(CM_OUTPUT_TAP);
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}
+