Add tests for FlBinaryMessenger, FlBasicMessageChannel, FlMethodChannel (#18597)

* Add mock implementations of the Flutter embedding API and EGL
* Add tests for FlBinaryMessenger, FlBasicMessageChannel, FlMethodChannel
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index ba413f5..9cde267 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -1191,10 +1191,12 @@
 FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.cc
 FILE: ../../../flutter/shell/platform/glfw/text_input_plugin.h
 FILE: ../../../flutter/shell/platform/linux/fl_basic_message_channel.cc
+FILE: ../../../flutter/shell/platform/linux/fl_basic_message_channel_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_binary_codec.cc
 FILE: ../../../flutter/shell/platform/linux/fl_binary_codec_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger.cc
 FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger_private.h
+FILE: ../../../flutter/shell/platform/linux/fl_binary_messenger_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_dart_project.cc
 FILE: ../../../flutter/shell/platform/linux/fl_dart_project_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_engine.cc
@@ -1211,6 +1213,7 @@
 FILE: ../../../flutter/shell/platform/linux/fl_method_call_private.h
 FILE: ../../../flutter/shell/platform/linux/fl_method_channel.cc
 FILE: ../../../flutter/shell/platform/linux/fl_method_channel_private.h
+FILE: ../../../flutter/shell/platform/linux/fl_method_channel_test.cc
 FILE: ../../../flutter/shell/platform/linux/fl_method_codec.cc
 FILE: ../../../flutter/shell/platform/linux/fl_method_codec_private.h
 FILE: ../../../flutter/shell/platform/linux/fl_method_codec_test.cc
diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn
index f4b5be5..6093371 100644
--- a/shell/platform/embedder/BUILD.gn
+++ b/shell/platform/embedder/BUILD.gn
@@ -24,7 +24,6 @@
   source_set(target_name) {
     sources = [
       "embedder.cc",
-      "embedder.h",
       "embedder_engine.cc",
       "embedder_engine.h",
       "embedder_external_texture_gl.cc",
@@ -75,6 +74,10 @@
       "//third_party/skia",
     ]
 
+    public_deps = [
+      ":embedder_headers",
+    ]
+
     public_configs += [ "//flutter:config" ]
   }
 }
@@ -87,6 +90,14 @@
   public_configs = [ ":embedder_prefix_config" ]
 }
 
+source_set("embedder_headers") {
+  public = [
+    "embedder.h",
+  ]
+
+  public_configs = [ "//flutter:config" ]
+}
+
 config("embedder_prefix_config") {
   defines = [ "FLUTTER_API_SYMBOL_PREFIX=Embedder" ]
 }
diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn
index 809d5a2..54c550d 100644
--- a/shell/platform/linux/BUILD.gn
+++ b/shell/platform/linux/BUILD.gn
@@ -70,9 +70,11 @@
   include_dirs = [ "public" ]
 }
 
-source_set("flutter_linux") {
+source_set("flutter_linux_sources") {
   public = _public_headers
 
+  configs += [ "//flutter/shell/platform/linux/config:gtk" ]
+
   sources = [
     "fl_basic_message_channel.cc",
     "fl_binary_codec.cc",
@@ -99,19 +101,29 @@
     "fl_view.cc",
   ]
 
+  # Set flag to stop headers being directly included (library users should not do this)
+  defines = [ "FLUTTER_LINUX_COMPILATION" ]
+
+  deps = [
+    "//flutter/shell/platform/embedder:embedder_headers",
+    "//third_party/rapidjson",
+  ]
+}
+
+source_set("flutter_linux") {
   configs += [
     "//flutter/shell/platform/linux/config:gtk",
     "//flutter/shell/platform/linux/config:egl",
     "//third_party/khronos:khronos_headers",
   ]
 
-  # Set flag to stop headers being directly included (library users should not do this)
-  defines = [ "FLUTTER_LINUX_COMPILATION" ]
+  public_deps = [
+    ":flutter_linux_sources",
+  ]
 
   deps = [
     "//flutter/shell/platform/common/cpp:common_cpp_input",
     "//flutter/shell/platform/embedder:embedder_with_symbol_prefix",
-    "//third_party/rapidjson",
   ]
 }
 
@@ -123,11 +135,14 @@
   testonly = true
 
   sources = [
+    "fl_basic_message_channel_test.cc",
     "fl_binary_codec_test.cc",
+    "fl_binary_messenger_test.cc",
     "fl_dart_project_test.cc",
     "fl_json_message_codec_test.cc",
     "fl_json_method_codec_test.cc",
     "fl_message_codec_test.cc",
+    "fl_method_channel_test.cc",
     "fl_method_codec_test.cc",
     "fl_method_response_test.cc",
     "fl_standard_message_codec_test.cc",
@@ -135,6 +150,9 @@
     "fl_string_codec_test.cc",
     "fl_value_test.cc",
     "testing/fl_test.cc",
+    "testing/mock_egl.cc",
+    "testing/mock_engine.cc",
+    "testing/mock_renderer.cc",
   ]
 
   public_configs = [ "//flutter:config" ]
@@ -145,8 +163,10 @@
   defines = [ "FLUTTER_LINUX_COMPILATION" ]
 
   deps = [
-    ":flutter_linux",
     ":flutter_linux_fixtures",
+    ":flutter_linux_sources",
+    "//flutter/runtime:libdart",
+    "//flutter/shell/platform/embedder:embedder_headers",
     "//flutter/testing",
   ]
 }
diff --git a/shell/platform/linux/fl_basic_message_channel_test.cc b/shell/platform/linux/fl_basic_message_channel_test.cc
new file mode 100644
index 0000000..002ea0e
--- /dev/null
+++ b/shell/platform/linux/fl_basic_message_channel_test.cc
@@ -0,0 +1,152 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Included first as it collides with the X11 headers.
+#include "gtest/gtest.h"
+
+#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_message_codec.h"
+#include "flutter/shell/platform/linux/testing/mock_renderer.h"
+
+// Creates a mock engine that responds to platform messages.
+static FlEngine* make_mock_engine() {
+  g_autoptr(FlDartProject) project = fl_dart_project_new("data");
+  g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
+  g_autoptr(FlEngine) engine = fl_engine_new(project, FL_RENDERER(renderer));
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_engine_start(engine, &error));
+  EXPECT_EQ(error, nullptr);
+
+  return static_cast<FlEngine*>(g_object_ref(engine));
+}
+
+// Called when the message response is received in the SendMessage test.
+static void echo_response_cb(GObject* object,
+                             GAsyncResult* result,
+                             gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlValue) message = fl_basic_message_channel_send_finish(
+      FL_BASIC_MESSAGE_CHANNEL(object), result, &error);
+  EXPECT_NE(message, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(message), "Hello World!");
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks sending a message works.
+TEST(FlBasicMessageChannelTest, SendMessage) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new();
+  g_autoptr(FlBasicMessageChannel) channel = fl_basic_message_channel_new(
+      messenger, "test/echo", FL_MESSAGE_CODEC(codec));
+  g_autoptr(FlValue) message = fl_value_new_string("Hello World!");
+  fl_basic_message_channel_send(channel, message, nullptr, echo_response_cb,
+                                loop);
+
+  // Blocks here until echo_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when the message response is received in the SendFailure test.
+static void failure_response_cb(GObject* object,
+                                GAsyncResult* result,
+                                gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlValue) message = fl_basic_message_channel_send_finish(
+      FL_BASIC_MESSAGE_CHANNEL(object), result, &error);
+  EXPECT_EQ(message, nullptr);
+  EXPECT_NE(error, nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the engine reporting a send failure is handled.
+TEST(FlBasicMessageChannelTest, SendFailure) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new();
+  g_autoptr(FlBasicMessageChannel) channel = fl_basic_message_channel_new(
+      messenger, "test/failure", FL_MESSAGE_CODEC(codec));
+  g_autoptr(FlValue) message = fl_value_new_string("Hello World!");
+  fl_basic_message_channel_send(channel, message, nullptr, failure_response_cb,
+                                loop);
+
+  // Blocks here until failure_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when a message is received from the engine in the ReceiveMessage test.
+static void message_cb(FlBasicMessageChannel* channel,
+                       FlValue* message,
+                       FlBasicMessageChannelResponseHandle* response_handle,
+                       gpointer user_data) {
+  EXPECT_NE(message, nullptr);
+  EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(message), "Marco!");
+
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlValue) response = fl_value_new_string("Polo!");
+  EXPECT_TRUE(fl_basic_message_channel_respond(channel, response_handle,
+                                               response, &error));
+  EXPECT_EQ(error, nullptr);
+}
+
+// Called when a the test engine notifies us what response we sent in the
+// ReceiveMessage test.
+static void response_cb(FlBasicMessageChannel* channel,
+                        FlValue* message,
+                        FlBasicMessageChannelResponseHandle* response_handle,
+                        gpointer user_data) {
+  EXPECT_NE(message, nullptr);
+  EXPECT_EQ(fl_value_get_type(message), FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(message), "Polo!");
+
+  fl_basic_message_channel_respond(channel, response_handle, nullptr, nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the shell able to receive and respond to messages from the engine.
+TEST(FlBasicMessageChannelTest, ReceiveMessage) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+
+  // Listen for messages from the engine.
+  g_autoptr(FlStandardMessageCodec) codec = fl_standard_message_codec_new();
+  g_autoptr(FlBasicMessageChannel) messages_channel =
+      fl_basic_message_channel_new(messenger, "test/messages",
+                                   FL_MESSAGE_CODEC(codec));
+  fl_basic_message_channel_set_message_handler(messages_channel, message_cb,
+                                               nullptr, nullptr);
+
+  // Listen for response from the engine.
+  g_autoptr(FlBasicMessageChannel) responses_channel =
+      fl_basic_message_channel_new(messenger, "test/responses",
+                                   FL_MESSAGE_CODEC(codec));
+  fl_basic_message_channel_set_message_handler(responses_channel, response_cb,
+                                               loop, nullptr);
+
+  // Triggger the engine to send a message.
+  g_autoptr(FlBasicMessageChannel) control_channel =
+      fl_basic_message_channel_new(messenger, "test/send-message",
+                                   FL_MESSAGE_CODEC(codec));
+  g_autoptr(FlValue) message = fl_value_new_string("Marco!");
+  fl_basic_message_channel_send(control_channel, message, nullptr, nullptr,
+                                nullptr);
+
+  // Blocks here until response_cb is called.
+  g_main_loop_run(loop);
+}
diff --git a/shell/platform/linux/fl_binary_messenger_test.cc b/shell/platform/linux/fl_binary_messenger_test.cc
new file mode 100644
index 0000000..edbd136
--- /dev/null
+++ b/shell/platform/linux/fl_binary_messenger_test.cc
@@ -0,0 +1,195 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Included first as it collides with the X11 headers.
+#include "gtest/gtest.h"
+
+#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
+#include "flutter/shell/platform/linux/testing/mock_renderer.h"
+
+// Creates a mock engine that responds to platform messages.
+static FlEngine* make_mock_engine() {
+  g_autoptr(FlDartProject) project = fl_dart_project_new("data");
+  g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
+  g_autoptr(FlEngine) engine = fl_engine_new(project, FL_RENDERER(renderer));
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_engine_start(engine, &error));
+  EXPECT_EQ(error, nullptr);
+
+  return static_cast<FlEngine*>(g_object_ref(engine));
+}
+
+// Checks sending nullptr for a message works.
+TEST(FlBinaryMessengerTest, SendNullptrMessage) {
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  fl_binary_messenger_send_on_channel(messenger, "test/echo", nullptr, nullptr,
+                                      nullptr, nullptr);
+}
+
+// Checks sending a zero length message works.
+TEST(FlBinaryMessengerTest, SendEmptyMessage) {
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(GBytes) message = g_bytes_new(nullptr, 0);
+  fl_binary_messenger_send_on_channel(messenger, "test/echo", message, nullptr,
+                                      nullptr, nullptr);
+}
+
+// Called when the message response is received in the SendMessage test.
+static void echo_response_cb(GObject* object,
+                             GAsyncResult* result,
+                             gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(GBytes) message = fl_binary_messenger_send_on_channel_finish(
+      FL_BINARY_MESSENGER(object), result, &error);
+  EXPECT_NE(message, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  g_autofree gchar* text =
+      g_strndup(static_cast<const gchar*>(g_bytes_get_data(message, nullptr)),
+                g_bytes_get_size(message));
+  EXPECT_STREQ(text, "Hello World!");
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks sending a message works.
+TEST(FlBinaryMessengerTest, SendMessage) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  const char* text = "Hello World!";
+  g_autoptr(GBytes) message = g_bytes_new(text, strlen(text));
+  fl_binary_messenger_send_on_channel(messenger, "test/echo", message, nullptr,
+                                      echo_response_cb, loop);
+
+  // Blocks here until echo_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when the message response is received in the NullptrResponse test.
+static void nullptr_response_cb(GObject* object,
+                                GAsyncResult* result,
+                                gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(GBytes) message = fl_binary_messenger_send_on_channel_finish(
+      FL_BINARY_MESSENGER(object), result, &error);
+  EXPECT_NE(message, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_EQ(g_bytes_get_size(message), static_cast<gsize>(0));
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the engine returning a nullptr message work.
+TEST(FlBinaryMessengerTest, NullptrResponse) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  const char* text = "Hello World!";
+  g_autoptr(GBytes) message = g_bytes_new(text, strlen(text));
+  fl_binary_messenger_send_on_channel(messenger, "test/nullptr-response",
+                                      message, nullptr, nullptr_response_cb,
+                                      loop);
+
+  // Blocks here until nullptr_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when the message response is received in the SendFailure test.
+static void failure_response_cb(GObject* object,
+                                GAsyncResult* result,
+                                gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(GBytes) message = fl_binary_messenger_send_on_channel_finish(
+      FL_BINARY_MESSENGER(object), result, &error);
+  EXPECT_EQ(message, nullptr);
+  EXPECT_NE(error, nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the engine reporting a send failure is handled.
+TEST(FlBinaryMessengerTest, SendFailure) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  fl_binary_messenger_send_on_channel(messenger, "test/failure", nullptr,
+                                      nullptr, failure_response_cb, loop);
+
+  // Blocks here until failure_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when a message is received from the engine in the ReceiveMessage test.
+static void message_cb(FlBinaryMessenger* messenger,
+                       const gchar* channel,
+                       GBytes* message,
+                       FlBinaryMessengerResponseHandle* response_handle,
+                       gpointer user_data) {
+  EXPECT_NE(message, nullptr);
+  g_autofree gchar* text =
+      g_strndup(static_cast<const gchar*>(g_bytes_get_data(message, nullptr)),
+                g_bytes_get_size(message));
+  EXPECT_STREQ(text, "Marco!");
+
+  const char* response_text = "Polo!";
+  g_autoptr(GBytes) response =
+      g_bytes_new(response_text, strlen(response_text));
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_binary_messenger_send_response(messenger, response_handle,
+                                                response, &error));
+  EXPECT_EQ(error, nullptr);
+}
+
+// Called when a the test engine notifies us what response we sent in the
+// ReceiveMessage test.
+static void response_cb(FlBinaryMessenger* messenger,
+                        const gchar* channel,
+                        GBytes* message,
+                        FlBinaryMessengerResponseHandle* response_handle,
+                        gpointer user_data) {
+  EXPECT_NE(message, nullptr);
+  g_autofree gchar* text =
+      g_strndup(static_cast<const gchar*>(g_bytes_get_data(message, nullptr)),
+                g_bytes_get_size(message));
+  EXPECT_STREQ(text, "Polo!");
+
+  fl_binary_messenger_send_response(messenger, response_handle, nullptr,
+                                    nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the shell able to receive and respond to messages from the engine.
+TEST(FlBinaryMessengerTest, ReceiveMessage) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+
+  // Listen for messages from the engine.
+  fl_binary_messenger_set_message_handler_on_channel(
+      messenger, "test/messages", message_cb, nullptr, nullptr);
+
+  // Listen for response from the engine.
+  fl_binary_messenger_set_message_handler_on_channel(
+      messenger, "test/responses", response_cb, loop, nullptr);
+
+  // Triggger the engine to send a message.
+  const char* text = "Marco!";
+  g_autoptr(GBytes) message = g_bytes_new(text, strlen(text));
+  fl_binary_messenger_send_on_channel(messenger, "test/send-message", message,
+                                      nullptr, nullptr, nullptr);
+
+  // Blocks here until response_cb is called.
+  g_main_loop_run(loop);
+}
diff --git a/shell/platform/linux/fl_method_channel_test.cc b/shell/platform/linux/fl_method_channel_test.cc
new file mode 100644
index 0000000..2a5ab6a
--- /dev/null
+++ b/shell/platform/linux/fl_method_channel_test.cc
@@ -0,0 +1,434 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Included first as it collides with the X11 headers.
+#include "gtest/gtest.h"
+
+#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
+#include "flutter/shell/platform/linux/fl_engine_private.h"
+#include "flutter/shell/platform/linux/fl_method_codec_private.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
+#include "flutter/shell/platform/linux/testing/mock_renderer.h"
+
+// Creates a mock engine that responds to platform messages.
+static FlEngine* make_mock_engine() {
+  g_autoptr(FlDartProject) project = fl_dart_project_new("data");
+  g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
+  g_autoptr(FlEngine) engine = fl_engine_new(project, FL_RENDERER(renderer));
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_engine_start(engine, &error));
+  EXPECT_EQ(error, nullptr);
+
+  return static_cast<FlEngine*>(g_object_ref(engine));
+}
+
+// Called when when the method call response is received in the InvokeMethod
+// test.
+static void method_response_cb(GObject* object,
+                               GAsyncResult* result,
+                               gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
+      FL_METHOD_CHANNEL(object), result, &error);
+  EXPECT_NE(response, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  FlValue* r = fl_method_response_get_result(response, &error);
+  EXPECT_NE(r, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_EQ(fl_value_get_type(r), FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(r), "Hello World!");
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks if invoking a method returns a value.
+TEST(FlMethodChannelTest, InvokeMethod) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
+      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
+
+  g_autoptr(FlValue) args = fl_value_new_string("Hello World!");
+  fl_method_channel_invoke_method(channel, "Echo", args, nullptr,
+                                  method_response_cb, loop);
+
+  // Blocks here until method_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when when the method call response is received in the
+// InvokeMethodNullptrArgsMessage test.
+static void nullptr_args_response_cb(GObject* object,
+                                     GAsyncResult* result,
+                                     gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
+      FL_METHOD_CHANNEL(object), result, &error);
+  EXPECT_NE(response, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  FlValue* r = fl_method_response_get_result(response, &error);
+  EXPECT_NE(r, nullptr);
+  EXPECT_EQ(error, nullptr);
+  EXPECT_EQ(fl_value_get_type(r), FL_VALUE_TYPE_NULL);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks if a method can be invoked with nullptr for arguments.
+TEST(FlMethodChannelTest, InvokeMethodNullptrArgsMessage) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
+      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
+
+  fl_method_channel_invoke_method(channel, "Echo", nullptr, nullptr,
+                                  nullptr_args_response_cb, loop);
+
+  // Blocks here until nullptr_args_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when when the method call response is received in the
+// InvokeMethodError test.
+static void error_response_cb(GObject* object,
+                              GAsyncResult* result,
+                              gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
+      FL_METHOD_CHANNEL(object), result, &error);
+  EXPECT_NE(response, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response));
+  EXPECT_STREQ(
+      fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)),
+      "CODE");
+  EXPECT_STREQ(
+      fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)),
+      "MESSAGE");
+  FlValue* details =
+      fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(response));
+  EXPECT_NE(details, nullptr);
+  EXPECT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(details), "DETAILS");
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks if an error response from a method call is handled.
+TEST(FlMethodChannelTest, InvokeMethodError) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
+      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
+
+  g_autoptr(FlValue) args = fl_value_new_list();
+  fl_value_append_take(args, fl_value_new_string("CODE"));
+  fl_value_append_take(args, fl_value_new_string("MESSAGE"));
+  fl_value_append_take(args, fl_value_new_string("DETAILS"));
+  fl_method_channel_invoke_method(channel, "Error", args, nullptr,
+                                  error_response_cb, loop);
+
+  // Blocks here until error_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when when the method call response is received in the
+// InvokeMethodNotImplemented test.
+static void not_implemented_response_cb(GObject* object,
+                                        GAsyncResult* result,
+                                        gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
+      FL_METHOD_CHANNEL(object), result, &error);
+  EXPECT_NE(response, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response));
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks if a not implemeneted response from a method call is handled.
+TEST(FlMethodChannelTest, InvokeMethodNotImplemented) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
+      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
+
+  fl_method_channel_invoke_method(channel, "NotImplemented", nullptr, nullptr,
+                                  not_implemented_response_cb, loop);
+
+  // Blocks here until not_implemented_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when when the method call response is received in the
+// InvokeMethodFailure test.
+static void failure_response_cb(GObject* object,
+                                GAsyncResult* result,
+                                gpointer user_data) {
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
+      FL_METHOD_CHANNEL(object), result, &error);
+  EXPECT_EQ(response, nullptr);
+  EXPECT_NE(error, nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks if an engine failure calling a method call is handled.
+TEST(FlMethodChannelTest, InvokeMethodFailure) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel =
+      fl_method_channel_new(messenger, "test/failure", FL_METHOD_CODEC(codec));
+
+  fl_method_channel_invoke_method(channel, "Echo", nullptr, nullptr,
+                                  failure_response_cb, loop);
+
+  // Blocks here until failure_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when a method call is received from the engine in the
+// ReceiveMethodCallRespondSuccess test.
+static void method_call_success_cb(FlMethodChannel* channel,
+                                   FlMethodCall* method_call,
+                                   gpointer user_data) {
+  EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo");
+  EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)),
+            FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)),
+               "Marco!");
+
+  g_autoptr(FlValue) result = fl_value_new_string("Polo!");
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_method_call_respond_success(method_call, result, &error));
+  EXPECT_EQ(error, nullptr);
+}
+
+// Called when a the test engine notifies us what response we sent in the
+// ReceiveMethodCallRespondSuccess test.
+static void method_call_success_response_cb(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    GBytes* message,
+    FlBinaryMessengerResponseHandle* response_handle,
+    gpointer user_data) {
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response =
+      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error);
+  EXPECT_NE(response, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
+  FlValue* result = fl_method_success_response_get_result(
+      FL_METHOD_SUCCESS_RESPONSE(response));
+  EXPECT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(result), "Polo!");
+
+  fl_binary_messenger_send_response(messenger, response_handle, nullptr,
+                                    nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the shell able to receive and respond to method calls from the engine.
+TEST(FlMethodChannelTest, ReceiveMethodCallRespondSuccess) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
+      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
+  fl_method_channel_set_method_call_handler(channel, method_call_success_cb,
+                                            nullptr, nullptr);
+
+  // Listen for response from the engine.
+  fl_binary_messenger_set_message_handler_on_channel(
+      messenger, "test/responses", method_call_success_response_cb, loop,
+      nullptr);
+
+  // Trigger the engine to make a method call.
+  g_autoptr(FlValue) args = fl_value_new_list();
+  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
+  fl_value_append_take(args, fl_value_new_string("Foo"));
+  fl_value_append_take(args, fl_value_new_string("Marco!"));
+  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
+                                  nullptr, loop);
+
+  // Blocks here until method_call_success_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when a method call is received from the engine in the
+// ReceiveMethodCallRespondError test.
+static void method_call_error_cb(FlMethodChannel* channel,
+                                 FlMethodCall* method_call,
+                                 gpointer user_data) {
+  EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo");
+  EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)),
+            FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)),
+               "Marco!");
+
+  g_autoptr(FlValue) details = fl_value_new_string("DETAILS");
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_method_call_respond_error(method_call, "CODE", "MESSAGE",
+                                           details, &error));
+  EXPECT_EQ(error, nullptr);
+}
+
+// Called when a the test engine notifies us what response we sent in the
+// ReceiveMethodCallRespondError test.
+static void method_call_error_response_cb(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    GBytes* message,
+    FlBinaryMessengerResponseHandle* response_handle,
+    gpointer user_data) {
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response =
+      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error);
+  EXPECT_NE(response, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response));
+  EXPECT_STREQ(
+      fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)),
+      "CODE");
+  EXPECT_STREQ(
+      fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)),
+      "MESSAGE");
+  FlValue* details =
+      fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(response));
+  EXPECT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(details), "DETAILS");
+
+  fl_binary_messenger_send_response(messenger, response_handle, nullptr,
+                                    nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the shell able to receive and respond to method calls from the engine.
+TEST(FlMethodChannelTest, ReceiveMethodCallRespondError) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
+      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
+  fl_method_channel_set_method_call_handler(channel, method_call_error_cb,
+                                            nullptr, nullptr);
+
+  // Listen for response from the engine.
+  fl_binary_messenger_set_message_handler_on_channel(
+      messenger, "test/responses", method_call_error_response_cb, loop,
+      nullptr);
+
+  // Trigger the engine to make a method call.
+  g_autoptr(FlValue) args = fl_value_new_list();
+  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
+  fl_value_append_take(args, fl_value_new_string("Foo"));
+  fl_value_append_take(args, fl_value_new_string("Marco!"));
+  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
+                                  nullptr, loop);
+
+  // Blocks here until method_call_error_response_cb is called.
+  g_main_loop_run(loop);
+}
+
+// Called when a method call is received from the engine in the
+// ReceiveMethodCallRespondNotImplemented test.
+static void method_call_not_implemented_cb(FlMethodChannel* channel,
+                                           FlMethodCall* method_call,
+                                           gpointer user_data) {
+  EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo");
+  EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)),
+            FL_VALUE_TYPE_STRING);
+  EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)),
+               "Marco!");
+
+  g_autoptr(GError) error = nullptr;
+  EXPECT_TRUE(fl_method_call_respond_not_implemented(method_call, &error));
+  EXPECT_EQ(error, nullptr);
+}
+
+// Called when a the test engine notifies us what response we sent in the
+// ReceiveMethodCallRespondNotImplemented test.
+static void method_call_not_implemented_response_cb(
+    FlBinaryMessenger* messenger,
+    const gchar* channel,
+    GBytes* message,
+    FlBinaryMessengerResponseHandle* response_handle,
+    gpointer user_data) {
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(FlMethodResponse) response =
+      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error);
+  EXPECT_NE(response, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response));
+
+  fl_binary_messenger_send_response(messenger, response_handle, nullptr,
+                                    nullptr);
+
+  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
+}
+
+// Checks the shell able to receive and respond to method calls from the engine.
+TEST(FlMethodChannelTest, ReceiveMethodCallRespondNotImplemented) {
+  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
+
+  g_autoptr(FlEngine) engine = make_mock_engine();
+  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
+      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
+  fl_method_channel_set_method_call_handler(
+      channel, method_call_not_implemented_cb, nullptr, nullptr);
+
+  // Listen for response from the engine.
+  fl_binary_messenger_set_message_handler_on_channel(
+      messenger, "test/responses", method_call_not_implemented_response_cb,
+      loop, nullptr);
+
+  // Trigger the engine to make a method call.
+  g_autoptr(FlValue) args = fl_value_new_list();
+  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
+  fl_value_append_take(args, fl_value_new_string("Foo"));
+  fl_value_append_take(args, fl_value_new_string("Marco!"));
+  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
+                                  nullptr, loop);
+
+  // Blocks here until method_call_not_implemented_response_cb is called.
+  g_main_loop_run(loop);
+}
diff --git a/shell/platform/linux/testing/mock_egl.cc b/shell/platform/linux/testing/mock_egl.cc
new file mode 100644
index 0000000..53edb69
--- /dev/null
+++ b/shell/platform/linux/testing/mock_egl.cc
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <EGL/egl.h>
+
+EGLBoolean eglBindAPI(EGLenum api) {
+  return EGL_TRUE;
+}
+
+EGLBoolean eglChooseConfig(EGLDisplay dpy,
+                           const EGLint* attrib_list,
+                           EGLConfig* configs,
+                           EGLint config_size,
+                           EGLint* num_config) {
+  return EGL_TRUE;
+}
+
+EGLContext eglCreateContext(EGLDisplay dpy,
+                            EGLConfig config,
+                            EGLContext share_context,
+                            const EGLint* attrib_list) {
+  return nullptr;
+}
+
+EGLSurface eglCreateWindowSurface(EGLDisplay dpy,
+                                  EGLConfig config,
+                                  EGLNativeWindowType win,
+                                  const EGLint* attrib_list) {
+  return nullptr;
+}
+
+EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id) {
+  return nullptr;
+}
+
+void (*eglGetProcAddress(const char* procname))(void) {
+  return nullptr;
+}
+
+EGLBoolean eglInitialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
+  if (major != nullptr)
+    *major = 1;
+  if (minor != nullptr)
+    *major = 5;
+  return EGL_TRUE;
+}
+
+EGLBoolean eglMakeCurrent(EGLDisplay dpy,
+                          EGLSurface draw,
+                          EGLSurface read,
+                          EGLContext ctx) {
+  return EGL_TRUE;
+}
+
+EGLBoolean eglQueryContext(EGLDisplay dpy,
+                           EGLContext ctx,
+                           EGLint attribute,
+                           EGLint* value) {
+  return EGL_TRUE;
+}
+
+EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) {
+  return EGL_TRUE;
+}
diff --git a/shell/platform/linux/testing/mock_engine.cc b/shell/platform/linux/testing/mock_engine.cc
new file mode 100644
index 0000000..3961cb0
--- /dev/null
+++ b/shell/platform/linux/testing/mock_engine.cc
@@ -0,0 +1,369 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/shell/platform/embedder/embedder.h"
+#include "flutter/shell/platform/linux/fl_method_codec_private.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_response.h"
+#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
+#include "gtest/gtest.h"
+
+#include <string.h>
+
+struct _FlutterEngine {
+  bool running;
+  FlutterPlatformMessageCallback platform_message_callback;
+  FlutterTaskRunnerPostTaskCallback platform_post_task_callback;
+  void* user_data;
+
+  _FlutterEngine(FlutterPlatformMessageCallback platform_message_callback,
+                 FlutterTaskRunnerPostTaskCallback platform_post_task_callback,
+                 void* user_data)
+      : running(false),
+        platform_message_callback(platform_message_callback),
+        platform_post_task_callback(platform_post_task_callback),
+        user_data(user_data) {}
+};
+
+struct _FlutterPlatformMessageResponseHandle {
+  FlutterDataCallback data_callback;
+  void* user_data;
+  std::string channel;
+  bool released;
+
+  // Constructor for a response handle generated by the engine.
+  _FlutterPlatformMessageResponseHandle(std::string channel)
+      : data_callback(nullptr),
+        user_data(nullptr),
+        channel(channel),
+        released(false) {}
+
+  // Constructor for a response handle generated by the shell.
+  _FlutterPlatformMessageResponseHandle(FlutterDataCallback data_callback,
+                                        void* user_data)
+      : data_callback(data_callback), user_data(user_data), released(false) {}
+};
+
+struct _FlutterTaskRunner {
+  uint64_t task;
+  std::string channel;
+  const FlutterPlatformMessageResponseHandle* response_handle;
+  uint8_t* message;
+  size_t message_size;
+
+  _FlutterTaskRunner(
+      uint64_t task,
+      const std::string& channel,
+      const FlutterPlatformMessageResponseHandle* response_handle,
+      const uint8_t* message,
+      size_t message_size)
+      : task(task),
+        channel(channel),
+        response_handle(response_handle),
+        message_size(message_size) {
+    this->message = static_cast<uint8_t*>(malloc(message_size));
+    memcpy(this->message, message, message_size);
+  }
+  ~_FlutterTaskRunner() {
+    if (response_handle != nullptr) {
+      EXPECT_TRUE(response_handle->released);
+      delete response_handle;
+    }
+    free(message);
+  }
+};
+
+// Send a response from the engine.
+static void send_response(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    const std::string& channel,
+    const FlutterPlatformMessageResponseHandle* response_handle,
+    const uint8_t* message,
+    size_t message_size) {
+  if (response_handle == nullptr)
+    return;
+
+  FlutterTask task;
+  task.runner = new _FlutterTaskRunner(1234, channel, response_handle, message,
+                                       message_size);
+  task.task = task.runner->task;
+  engine->platform_post_task_callback(task, 0, engine->user_data);
+}
+
+// Send a message from the engine.
+static void send_message(FLUTTER_API_SYMBOL(FlutterEngine) engine,
+                         const std::string& channel,
+                         const uint8_t* message,
+                         size_t message_size) {
+  FlutterTask task;
+  task.runner =
+      new _FlutterTaskRunner(1234, channel, nullptr, message, message_size);
+  task.task = task.runner->task;
+  engine->platform_post_task_callback(task, 0, engine->user_data);
+}
+
+static void invoke_method(FLUTTER_API_SYMBOL(FlutterEngine) engine,
+                          const std::string& channel,
+                          const gchar* name,
+                          FlValue* args) {
+  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+  g_autoptr(GError) error = nullptr;
+  g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
+      FL_METHOD_CODEC(codec), name, args, &error);
+  EXPECT_NE(message, nullptr);
+  EXPECT_EQ(error, nullptr);
+
+  FlutterTask task;
+  task.runner = new _FlutterTaskRunner(
+      1234, channel, nullptr,
+      static_cast<const uint8_t*>(g_bytes_get_data(message, nullptr)),
+      g_bytes_get_size(message));
+  task.task = task.runner->task;
+  engine->platform_post_task_callback(task, 0, engine->user_data);
+}
+
+FlutterEngineResult FlutterEngineRun(size_t version,
+                                     const FlutterRendererConfig* config,
+                                     const FlutterProjectArgs* args,
+                                     void* user_data,
+                                     FLUTTER_API_SYMBOL(FlutterEngine) *
+                                         engine_out) {
+  EXPECT_NE(config, nullptr);
+  EXPECT_NE(args, nullptr);
+  EXPECT_NE(user_data, nullptr);
+  EXPECT_NE(engine_out, nullptr);
+
+  FlutterEngineResult result =
+      FlutterEngineInitialize(version, config, args, user_data, engine_out);
+  if (result != kSuccess)
+    return result;
+  return FlutterEngineRunInitialized(*engine_out);
+}
+
+FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine)
+                                              engine) {
+  delete engine;
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterEngineInitialize(size_t version,
+                                            const FlutterRendererConfig* config,
+                                            const FlutterProjectArgs* args,
+                                            void* user_data,
+                                            FLUTTER_API_SYMBOL(FlutterEngine) *
+                                                engine_out) {
+  EXPECT_NE(config, nullptr);
+
+  EXPECT_NE(args, nullptr);
+  EXPECT_NE(args->platform_message_callback, nullptr);
+  EXPECT_NE(args->custom_task_runners, nullptr);
+  EXPECT_NE(args->custom_task_runners->platform_task_runner, nullptr);
+  EXPECT_NE(args->custom_task_runners->platform_task_runner->post_task_callback,
+            nullptr);
+
+  EXPECT_NE(user_data, nullptr);
+
+  EXPECT_EQ(config->type, kOpenGL);
+
+  *engine_out = new _FlutterEngine(
+      args->platform_message_callback,
+      args->custom_task_runners->platform_task_runner->post_task_callback,
+      user_data);
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine)
+                                                  engine) {
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterEngineRunInitialized(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine) {
+  engine->running = true;
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterEngineSendWindowMetricsEvent(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    const FlutterWindowMetricsEvent* event) {
+  EXPECT_TRUE(engine->running);
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterEngineSendPointerEvent(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    const FlutterPointerEvent* events,
+    size_t events_count) {
+  return kSuccess;
+}
+
+FLUTTER_EXPORT
+FlutterEngineResult FlutterEngineSendPlatformMessage(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    const FlutterPlatformMessage* message) {
+  EXPECT_TRUE(engine->running);
+
+  if (strcmp(message->channel, "test/echo") == 0) {
+    // Responds with the same message received.
+    send_response(engine, message->channel, message->response_handle,
+                  message->message, message->message_size);
+  } else if (strcmp(message->channel, "test/send-message") == 0) {
+    // Triggers the engine to send a message.
+    send_response(engine, message->channel, message->response_handle, nullptr,
+                  0);
+    send_message(engine, "test/messages", message->message,
+                 message->message_size);
+  } else if (strcmp(message->channel, "test/standard-method") == 0) {
+    g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
+    g_autoptr(GBytes) m = g_bytes_new(message->message, message->message_size);
+    g_autofree gchar* name = nullptr;
+    g_autoptr(FlValue) args = nullptr;
+    g_autoptr(GError) error = nullptr;
+    EXPECT_TRUE(fl_method_codec_decode_method_call(FL_METHOD_CODEC(codec), m,
+                                                   &name, &args, &error));
+    EXPECT_EQ(error, nullptr);
+
+    g_autoptr(GBytes) response = nullptr;
+    if (strcmp(name, "Echo") == 0) {
+      // Returns args as a success result.
+      response = fl_method_codec_encode_success_envelope(FL_METHOD_CODEC(codec),
+                                                         args, &error);
+      EXPECT_EQ(error, nullptr);
+    } else if (strcmp(name, "Error") == 0) {
+      // Returns an error result.
+      const gchar* code = nullptr;
+      const gchar* message = nullptr;
+      FlValue* details = nullptr;
+      if (fl_value_get_length(args) >= 2) {
+        FlValue* code_value = fl_value_get_list_value(args, 0);
+        EXPECT_EQ(fl_value_get_type(code_value), FL_VALUE_TYPE_STRING);
+        code = fl_value_get_string(code_value);
+        FlValue* message_value = fl_value_get_list_value(args, 1);
+        message = fl_value_get_type(message_value) == FL_VALUE_TYPE_STRING
+                      ? fl_value_get_string(message_value)
+                      : nullptr;
+      }
+      if (fl_value_get_length(args) >= 3)
+        details = fl_value_get_list_value(args, 2);
+      response = fl_method_codec_encode_error_envelope(
+          FL_METHOD_CODEC(codec), code, message, details, &error);
+      EXPECT_EQ(error, nullptr);
+    } else if (strcmp(name, "InvokeMethod") == 0) {
+      // Gets the engine to call the shell.
+      if (fl_value_get_length(args) == 3) {
+        FlValue* channel_value = fl_value_get_list_value(args, 0);
+        EXPECT_EQ(fl_value_get_type(channel_value), FL_VALUE_TYPE_STRING);
+        const gchar* channel = fl_value_get_string(channel_value);
+        FlValue* name_value = fl_value_get_list_value(args, 1);
+        EXPECT_EQ(fl_value_get_type(name_value), FL_VALUE_TYPE_STRING);
+        const gchar* name = fl_value_get_string(name_value);
+        FlValue* method_args = fl_value_get_list_value(args, 2);
+        invoke_method(engine, channel, name, method_args);
+      }
+      response = fl_method_codec_encode_success_envelope(FL_METHOD_CODEC(codec),
+                                                         nullptr, &error);
+      EXPECT_EQ(error, nullptr);
+    } else {
+      // Returns "not implemented".
+      response = g_bytes_new(nullptr, 0);
+    }
+
+    send_response(
+        engine, message->channel, message->response_handle,
+        static_cast<const uint8_t*>(g_bytes_get_data(response, nullptr)),
+        g_bytes_get_size(response));
+  } else if (strcmp(message->channel, "test/nullptr-response") == 0) {
+    // Sends a null response.
+    send_response(engine, message->channel, message->response_handle, nullptr,
+                  0);
+  } else if (strcmp(message->channel, "test/failure") == 0) {
+    // Generates an internal error.
+    return kInternalInconsistency;
+  }
+
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterPlatformMessageCreateResponseHandle(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    FlutterDataCallback data_callback,
+    void* user_data,
+    FlutterPlatformMessageResponseHandle** response_out) {
+  EXPECT_TRUE(engine->running);
+  EXPECT_NE(data_callback, nullptr);
+  EXPECT_NE(user_data, nullptr);
+
+  _FlutterPlatformMessageResponseHandle* handle =
+      new _FlutterPlatformMessageResponseHandle(data_callback, user_data);
+
+  *response_out = handle;
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    FlutterPlatformMessageResponseHandle* response) {
+  EXPECT_NE(engine, nullptr);
+  EXPECT_NE(response, nullptr);
+
+  EXPECT_TRUE(engine->running);
+
+  EXPECT_FALSE(response->released);
+  response->released = true;
+
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterEngineSendPlatformMessageResponse(
+    FLUTTER_API_SYMBOL(FlutterEngine) engine,
+    const FlutterPlatformMessageResponseHandle* handle,
+    const uint8_t* data,
+    size_t data_length) {
+  EXPECT_NE(engine, nullptr);
+  EXPECT_NE(handle, nullptr);
+
+  EXPECT_TRUE(engine->running);
+
+  // Send a message so the shell can check the responses received.
+  if (handle->channel != "test/responses")
+    send_message(engine, "test/responses", data, data_length);
+
+  EXPECT_FALSE(handle->released);
+
+  delete handle;
+
+  return kSuccess;
+}
+
+FlutterEngineResult FlutterEngineRunTask(FLUTTER_API_SYMBOL(FlutterEngine)
+                                             engine,
+                                         const FlutterTask* task) {
+  EXPECT_NE(engine, nullptr);
+  EXPECT_NE(task, nullptr);
+  EXPECT_NE(task->runner, nullptr);
+
+  FlutterTaskRunner runner = task->runner;
+  EXPECT_NE(runner, nullptr);
+  const FlutterPlatformMessageResponseHandle* response_handle =
+      runner->response_handle;
+  if (response_handle != nullptr) {
+    EXPECT_NE(response_handle->data_callback, nullptr);
+    response_handle->data_callback(runner->message, runner->message_size,
+                                   response_handle->user_data);
+  } else {
+    _FlutterPlatformMessageResponseHandle* handle =
+        new _FlutterPlatformMessageResponseHandle(runner->channel);
+
+    FlutterPlatformMessage message;
+    message.struct_size = sizeof(FlutterPlatformMessage);
+    message.channel = runner->channel.c_str();
+    message.message = runner->message;
+    message.message_size = runner->message_size;
+    message.response_handle = handle;
+    engine->platform_message_callback(&message, engine->user_data);
+  }
+
+  delete runner;
+
+  return kSuccess;
+}
diff --git a/shell/platform/linux/testing/mock_renderer.cc b/shell/platform/linux/testing/mock_renderer.cc
new file mode 100644
index 0000000..03ab548
--- /dev/null
+++ b/shell/platform/linux/testing/mock_renderer.cc
@@ -0,0 +1,27 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/shell/platform/linux/testing/mock_renderer.h"
+
+struct _FlMockRenderer {
+  FlRenderer parent_instance;
+};
+
+G_DEFINE_TYPE(FlMockRenderer, fl_mock_renderer, fl_renderer_get_type())
+
+// Implements FlRenderer::start
+static gboolean fl_mock_renderer_start(FlRenderer* renderer, GError** error) {
+  return TRUE;
+}
+
+static void fl_mock_renderer_class_init(FlMockRendererClass* klass) {
+  FL_RENDERER_CLASS(klass)->start = fl_mock_renderer_start;
+}
+
+static void fl_mock_renderer_init(FlMockRenderer* self) {}
+
+// Creates a stub renderer
+FlMockRenderer* fl_mock_renderer_new() {
+  return FL_MOCK_RENDERER(g_object_new(fl_mock_renderer_get_type(), nullptr));
+}
diff --git a/shell/platform/linux/testing/mock_renderer.h b/shell/platform/linux/testing/mock_renderer.h
new file mode 100644
index 0000000..1926868
--- /dev/null
+++ b/shell/platform/linux/testing/mock_renderer.h
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/shell/platform/linux/fl_renderer.h"
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(FlMockRenderer,
+                     fl_mock_renderer,
+                     FL,
+                     MOCK_RENDERER,
+                     FlRenderer)
+
+FlMockRenderer* fl_mock_renderer_new();
+
+G_END_DECLS