diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 10b8f3e..6e6b57f 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -107,6 +107,8 @@
 { 'command': 'guest-sync', 'data': { 'arg': 'any' }, 'returns': 'any' }
 { 'command': 'boxed', 'box': true, 'data': 'UserDefZero' }
 { 'command': 'boxed2', 'data': 'UserDefNativeListUnion', 'box': true }
+{ 'command': 'user-async', 'data': { 'a': 'int', '*b': 'int' },
+  'returns': 'UserDefB', 'async': true  }
 
 # For testing integer range flattening in opts-visitor. The following schema
 # corresponds to the option format:
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index b3b4cb8..4530e3d 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -48,6 +48,9 @@ object :obj-uint64List-wrapper
     member data: uint64List optional=False
 object :obj-uint8List-wrapper
     member data: uint8List optional=False
+object :obj-user-async-arg
+    member a: int optional=False
+    member b: int optional=True
 object :obj-user_def_cmd1-arg
     member ud1a: UserDefOne optional=False
 object :obj-user_def_cmd2-arg
@@ -196,6 +199,8 @@ command boxed2 UserDefNativeListUnion -> None
    gen=True success_response=True box=True
 command guest-sync :obj-guest-sync-arg -> any
    gen=True success_response=True box=False
+command user-async :obj-user-async-arg -> UserDefB
+   gen=True success_response=True box=False async
 command user_def_cmd None -> None
    gen=True success_response=True box=False
 command user_def_cmd1 :obj-user_def_cmd1-arg -> None
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index e9fc02d..dd2662d 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -76,6 +76,35 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
     return ret;
 }
 
+static GMainLoop *loop;
+
+static struct AsyncRet {
+    QmpReturn *qret;
+    UserDefB *ret;
+} async_ret;
+
+static gboolean qmp_user_async_idle(gpointer data)
+{
+    struct AsyncRet *ret = (struct AsyncRet *)data;
+
+    qmp_user_async_return(ret->qret, ret->ret);
+
+    g_main_loop_quit(loop);
+
+    return FALSE;
+}
+
+void qmp_user_async(int64_t a, bool has_b, int64_t b, QmpReturn *qret)
+{
+    UserDefB *ret = g_new0(UserDefB, 1);
+
+    ret->intb = a + (has_b ? b : 0);
+
+    async_ret.qret = qret;
+    async_ret.ret = ret;
+    g_idle_add(qmp_user_async_idle, &async_ret);
+}
+
 static void dispatch_cmd_return(QmpClient *client, QObject *resp)
 {
     assert(resp != NULL);
@@ -121,12 +150,15 @@ static void test_dispatch_cmd_error(void)
 }
 
 static QObject *ret;
+static char *ret_id;
 
 static void qmp_dispatch_return(QmpClient *client, QObject *resp_obj)
 {
     QDict *resp = qobject_to_qdict(resp_obj);
     assert(resp && !qdict_haskey(resp, "error"));
     ret = qdict_get(resp, "return");
+    g_free(ret_id);
+    ret_id = g_strdup(qdict_get_try_str(resp, "id"));
     assert(ret);
     qobject_incref(ret);
 }
@@ -193,6 +225,34 @@ static void test_dispatch_cmd_io(void)
     QDECREF(req);
 }
 
+static void test_dispatch_cmd_async(void)
+{
+    QmpClient client;
+    QDict *dret, *req = qdict_new();
+    QDict *args = qdict_new();
+
+    loop = g_main_loop_new(NULL, FALSE);
+    qmp_client_init(&client, qmp_dispatch_return);
+
+    qdict_put(args, "a", qint_from_int(99));
+    qdict_put(req, "arguments", args);
+    qdict_put(req, "id", qstring_from_str("foo99"));
+    qdict_put(req, "execute", qstring_from_str("user-async"));
+
+    qmp_dispatch(&client, QOBJECT(req), NULL);
+
+    g_main_loop_run(loop);
+    g_main_loop_unref(loop);
+
+    g_assert_cmpstr(ret_id, ==, "foo99");
+    dret = qobject_to_qdict(ret);
+    assert(qdict_get_int(dret, "intb") == 99);
+    QDECREF(dret);
+
+    qmp_client_destroy(&client);
+    QDECREF(req);
+}
+
 /* test generated dealloc functions for generated types */
 static void test_dealloc_types(void)
 {
@@ -264,6 +324,7 @@ int main(int argc, char **argv)
     g_test_add_func("/qmp/dispatch_cmd", test_dispatch_cmd);
     g_test_add_func("/qmp/dispatch_cmd_error", test_dispatch_cmd_error);
     g_test_add_func("/qmp/dispatch_cmd_io", test_dispatch_cmd_io);
+    g_test_add_func("/qmp/dispatch_cmd_async", test_dispatch_cmd_async);
     g_test_add_func("/qmp/dealloc_types", test_dealloc_types);
     g_test_add_func("/qmp/dealloc_partial", test_dealloc_partial);
 
