diff --git a/libpurple/purpleprotocolroster.h b/libpurple/purpleprotocolroster.h
--- a/libpurple/purpleprotocolroster.h
+++ b/libpurple/purpleprotocolroster.h
@@ -35,6 +35,17 @@
 #include "purpleprotocol.h"
 #include "purpleversion.h"
 
+/**
+ * PURPLE_PROTOCOL_ROSTER_ERROR:
+ *
+ * A #GError domain for protocol roster errors.
+ *
+ * Since: 3.0
+ */
+#define PURPLE_PROTOCOL_ROSTER_ERROR \
+	(g_quark_from_static_string("purple-protocol-roster")) \
+	PURPLE_AVAILABLE_MACRO_IN_3_0
+
 #define PURPLE_TYPE_PROTOCOL_ROSTER (purple_protocol_roster_get_type())
 
 PURPLE_AVAILABLE_IN_3_0
# HG changeset patch
# Node ID 3930c4d7753728db5f5d3cb32379e88c4392b8b3
# Parent  59e6529a1e788166a4c6f31ad31885fd841e13ae
diff --git a/libpurple/purpleprotocolroster.c b/libpurple/purpleprotocolroster.c
--- a/libpurple/purpleprotocolroster.c
+++ b/libpurple/purpleprotocolroster.c
@@ -54,8 +54,11 @@
 		iface->add_async(roster, account, contact, cancellable, callback,
 		                 data);
 	} else {
-		g_warning("%s does not implement the add_async method",
-		          G_OBJECT_TYPE_NAME(roster));
+		g_task_report_new_error(G_OBJECT(roster), callback, data,
+		                        purple_protocol_roster_add_async,
+		                        PURPLE_PROTOCOL_ROSTER_ERROR, 0,
+		                        "%s does not implement the add_async method",
+		                        G_OBJECT_TYPE_NAME(roster));
 	}
 }
 
@@ -66,12 +69,20 @@
 	PurpleProtocolRosterInterface *iface = NULL;
 
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL_ROSTER(roster), FALSE);
+	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
+
+	if(g_async_result_is_tagged(result, purple_protocol_roster_add_async)) {
+		return g_task_propagate_boolean(G_TASK(result), error);
+	}
 
 	iface = PURPLE_PROTOCOL_ROSTER_GET_IFACE(roster);
 	if(iface != NULL && iface->add_finish != NULL) {
 		return iface->add_finish(roster, result, error);
 	}
 
+	g_warning("purple_protocol_roster_add_finish called without calling "
+	          "purple_protocol_roster_add_async");
+
 	return FALSE;
 }
 
@@ -93,8 +104,11 @@
 		iface->update_async(roster, account, contact, cancellable, callback,
 		                    data);
 	} else {
-		g_warning("%s does not implement the update_async method",
-		          G_OBJECT_TYPE_NAME(roster));
+		g_task_report_new_error(G_OBJECT(roster), callback, data,
+		                        purple_protocol_roster_update_async,
+		                        PURPLE_PROTOCOL_ROSTER_ERROR, 0,
+		                        "%s does not implement the update_async method",
+		                        G_OBJECT_TYPE_NAME(roster));
 	}
 }
 
@@ -105,12 +119,20 @@
 	PurpleProtocolRosterInterface *iface = NULL;
 
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL_ROSTER(roster), FALSE);
+	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
+
+	if(g_async_result_is_tagged(result, purple_protocol_roster_update_async)) {
+		return g_task_propagate_boolean(G_TASK(result), error);
+	}
 
 	iface = PURPLE_PROTOCOL_ROSTER_GET_IFACE(roster);
 	if(iface != NULL && iface->update_finish != NULL) {
 		return iface->update_finish(roster, result, error);
 	}
 
+	g_warning("purple_protocol_roster_update_finish called without calling "
+	          "purple_protocol_roster_update_async");
+
 	return FALSE;
 }
 
@@ -132,8 +154,11 @@
 		iface->remove_async(roster, account, contact, cancellable, callback,
 		                    data);
 	} else {
-		g_warning("%s does not implement the remove_async method",
-		          G_OBJECT_TYPE_NAME(roster));
+		g_task_report_new_error(G_OBJECT(roster), callback, data,
+		                        purple_protocol_roster_remove_async,
+		                        PURPLE_PROTOCOL_ROSTER_ERROR, 0,
+		                        "%s does not implement the remove_async method",
+		                        G_OBJECT_TYPE_NAME(roster));
 	}
 }
 
@@ -144,11 +169,19 @@
 	PurpleProtocolRosterInterface *iface = NULL;
 
 	g_return_val_if_fail(PURPLE_IS_PROTOCOL_ROSTER(roster), FALSE);
+	g_return_val_if_fail(G_IS_ASYNC_RESULT(result), FALSE);
+
+	if(g_async_result_is_tagged(result, purple_protocol_roster_remove_async)) {
+		return g_task_propagate_boolean(G_TASK(result), error);
+	}
 
 	iface = PURPLE_PROTOCOL_ROSTER_GET_IFACE(roster);
 	if(iface != NULL && iface->remove_finish != NULL) {
 		return iface->remove_finish(roster, result, error);
 	}
 
+	g_warning("purple_protocol_roster_remove_finish called without calling "
+	          "purple_protocol_roster_remove_async");
+
 	return FALSE;
 }
diff --git a/libpurple/tests/test_protocol_roster.c b/libpurple/tests/test_protocol_roster.c
--- a/libpurple/tests/test_protocol_roster.c
+++ b/libpurple/tests/test_protocol_roster.c
@@ -85,78 +85,125 @@
  * TestProtocolRosterEmpty Tests
  *****************************************************************************/
 static void
+test_purple_protocol_roster_empty_add_cb(GObject *source, GAsyncResult *result,
+                                         G_GNUC_UNUSED gpointer data)
+{
+	GError *error = NULL;
+	gboolean success = FALSE;
+
+	g_assert_true(PURPLE_IS_PROTOCOL_ROSTER(source));
+	g_assert_true(G_IS_ASYNC_RESULT(result));
+
+	success = purple_protocol_roster_add_finish(PURPLE_PROTOCOL_ROSTER(source),
+	                                            result, &error);
+	g_assert_error(error, PURPLE_PROTOCOL_ROSTER_ERROR, 0);
+	g_clear_error(&error);
+	g_assert_false(success);
+}
+
+static void
 test_purple_protocol_roster_empty_add(void) {
-	if(g_test_subprocess()) {
-		PurpleAccount *account = NULL;
-		PurpleContact *contact = NULL;
-		PurpleProtocolRoster *roster = NULL;
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+	PurpleProtocolRoster *roster = NULL;
 
-		roster = g_object_new(test_purple_protocol_roster_empty_get_type(),
-		                      NULL);
+	roster = g_object_new(test_purple_protocol_roster_empty_get_type(),
+	                      NULL);
+
+	account = purple_account_new("test", "test");
+	contact = purple_contact_new(account, NULL);
 
-		account = purple_account_new("test", "test");
-		contact = purple_contact_new(account, NULL);
+	purple_protocol_roster_add_async(roster, account, contact, NULL,
+	                                 test_purple_protocol_roster_empty_add_cb,
+	                                 NULL);
 
-		purple_protocol_roster_add_async(roster, account, contact, NULL, NULL,
-		                                 NULL);
+	g_main_context_iteration(NULL, FALSE);
+
+	g_clear_object(&account);
+	g_assert_finalize_object(contact);
+	g_assert_finalize_object(roster);
+}
 
-		g_clear_object(&roster);
-		g_clear_object(&account);
-		g_clear_object(&contact);
-	}
+static void
+test_purple_protocol_roster_empty_update_cb(GObject *source,
+                                            GAsyncResult *result,
+                                            G_GNUC_UNUSED gpointer data)
+{
+	GError *error = NULL;
+	gboolean success = FALSE;
 
-	g_test_trap_subprocess(NULL, 0, 0);
-	g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolRosterEmpty*add_async*");
+	g_assert_true(PURPLE_IS_PROTOCOL_ROSTER(source));
+	g_assert_true(G_IS_ASYNC_RESULT(result));
+
+	success = purple_protocol_roster_update_finish(PURPLE_PROTOCOL_ROSTER(source),
+	                                               result, &error);
+	g_assert_error(error, PURPLE_PROTOCOL_ROSTER_ERROR, 0);
+	g_clear_error(&error);
+	g_assert_false(success);
 }
 
 static void
 test_purple_protocol_roster_empty_update(void) {
-	if(g_test_subprocess()) {
-		PurpleAccount *account = NULL;
-		PurpleContact *contact = NULL;
-		PurpleProtocolRoster *roster = NULL;
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+	PurpleProtocolRoster *roster = NULL;
+
+	roster = g_object_new(test_purple_protocol_roster_empty_get_type(),
+	                      NULL);
 
-		roster = g_object_new(test_purple_protocol_roster_empty_get_type(),
-		                      NULL);
+	account = purple_account_new("test", "test");
+	contact = purple_contact_new(account, NULL);
 
-		account = purple_account_new("test", "test");
-		contact = purple_contact_new(account, NULL);
+	purple_protocol_roster_update_async(roster, account, contact, NULL,
+	                                    test_purple_protocol_roster_empty_update_cb,
+	                                    NULL);
+
+	g_main_context_iteration(NULL, FALSE);
 
-		purple_protocol_roster_update_async(roster, account, contact, NULL,
-		                                    NULL, NULL);
+	g_clear_object(&account);
+	g_assert_finalize_object(contact);
+	g_assert_finalize_object(roster);
+}
 
-		g_clear_object(&roster);
-		g_clear_object(&account);
-		g_clear_object(&contact);
-	}
+static void
+test_purple_protocol_roster_empty_remove_cb(GObject *source,
+                                            GAsyncResult *result,
+                                            G_GNUC_UNUSED gpointer data)
+{
+	GError *error = NULL;
+	gboolean success = FALSE;
 
-	g_test_trap_subprocess(NULL, 0, 0);
-	g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolRosterEmpty*update_async*");
+	g_assert_true(PURPLE_IS_PROTOCOL_ROSTER(source));
+	g_assert_true(G_IS_ASYNC_RESULT(result));
+
+	success = purple_protocol_roster_remove_finish(PURPLE_PROTOCOL_ROSTER(source),
+	                                               result, &error);
+	g_assert_error(error, PURPLE_PROTOCOL_ROSTER_ERROR, 0);
+	g_clear_error(&error);
+	g_assert_false(success);
 }
 
 static void
 test_purple_protocol_roster_empty_remove(void) {
-	if(g_test_subprocess()) {
-		PurpleAccount *account = NULL;
-		PurpleContact *contact = NULL;
-		PurpleProtocolRoster *roster = NULL;
+	PurpleAccount *account = NULL;
+	PurpleContact *contact = NULL;
+	PurpleProtocolRoster *roster = NULL;
 
-		roster = g_object_new(test_purple_protocol_roster_empty_get_type(),
-		                      NULL);
+	roster = g_object_new(test_purple_protocol_roster_empty_get_type(),
+	                      NULL);
 
-		account = purple_account_new("test", "test");
-		contact = purple_contact_new(account, NULL);
+	account = purple_account_new("test", "test");
+	contact = purple_contact_new(account, NULL);
 
-		purple_protocol_roster_remove_async(roster, account, contact, NULL,
-		                                    NULL, NULL);
+	purple_protocol_roster_remove_async(roster, account, contact, NULL,
+	                                    test_purple_protocol_roster_empty_remove_cb,
+	                                    NULL);
 
-		g_clear_object(&roster);
-		g_clear_object(&account);
-		g_clear_object(&contact);
-	}
+	g_main_context_iteration(NULL, FALSE);
 
-	g_test_trap_subprocess(NULL, 0, 0);
-	g_test_trap_assert_stderr("*Purple-WARNING*TestPurpleProtocolRosterEmpty*remove_async*");
+	g_clear_object(&account);
+	g_assert_finalize_object(contact);
+	g_assert_finalize_object(roster);
 }
 
 /******************************************************************************
@@ -588,6 +635,8 @@
 
 	g_test_init(&argc, &argv, NULL);
 
+	g_test_set_nonfatal_assertions();
+
 	test_ui_purple_init();
 
 	loop = g_main_loop_new(NULL, FALSE);
