Testing FIDL protocols

Prerequisites

This tutorial builds on the HLCPP getting started tutorials.

Overview

This tutorial walks through the process of writing a test for the Echo.EchoString method. This tutorial shows you how to use the two utilities available for testing FIDL protocols implemented in HLCPP:

  • The gtest test loop fixture sys::testing::ComponentContextProvider.
  • The fidl_test_base.h file provided by the HLCPP bindings

If you want to write the code yourself, delete the following directories:

rm -r examples/fidl/hlcpp/testing/*

The test will be written in examples/fidl/hlcpp/testing/main.cc.

Set up dependencies

To set up dependencies:

  1. Include the libraries that are needed for the test:

    #include <fuchsia/examples/cpp/fidl.h>
    #include <fuchsia/examples/cpp/fidl_test_base.h>
    #include <lib/fidl/cpp/binding.h>
    #include <lib/sys/cpp/testing/component_context_provider.h>
    
    #include "src/lib/testing/loop_fixture/test_loop_fixture.h"
    
  2. Add a build rule for the test in examples/fidl/hlcpp/testing/BUILD.gn:

    # Copyright 2020 The Fuchsia Authors. All rights reserved.
    # Use of this source code is governed by a BSD-style license that can be
    # found in the LICENSE file.
    import("//build/components.gni")
    
    executable("bin") {
      testonly = true
      output_name = "example_hlcpp_protocol_test"
      sources = [ "main.cc" ]
      deps = [
        "//examples/fidl/fuchsia.examples:fuchsia.examples_hlcpp",
        "//sdk/lib/fidl/cpp",
        "//sdk/lib/sys/cpp",
        "//sdk/lib/sys/cpp/testing:unit",
        "//src/lib/fxl/test:gtest_main",
        "//src/lib/testing/loop_fixture",
      ]
    }
    
    fuchsia_unittest_package("example-hlcpp-protocol-test") {
      deps = [ ":bin" ]
    }
    
    group("hermetic_tests") {
      testonly = true
      deps = [ ":example-hlcpp-protocol-test" ]
    }
    
    

Create a server implementation

To create a server implementation:

  1. Add an implementation for the Echo protocol that is tested:

    class EchoImpl : public fuchsia::examples::testing::Echo_TestBase {
     public:
      void EchoString(std::string value, EchoStringCallback callback) override { callback(value); }
      void NotImplemented_(const std::string& name) override {
        std::cout << "Not implemented: " << name << std::endl;
      }
    };
    

    Rather than inheriting from fuchsia::examples::Echo, this implementation inherits from the corresponding test base class. This means that the implementation only needs to override the methods that are being tested (in this case, EchoString), as well as the NotImplemented_ method, which is called if any of the request handler methods that are not overridden get called.

  2. Create a test class that wraps the logic of publishing the echo protocol:

    class EchoServerInstance {
     public:
      explicit EchoServerInstance(std::unique_ptr<sys::ComponentContext> context) {
        context_ = std::move(context);
        binding_ = std::make_unique<fidl::Binding<fuchsia::examples::Echo>>(&impl_);
        fidl::InterfaceRequestHandler<fuchsia::examples::Echo> handler =
            [&](fidl::InterfaceRequest<fuchsia::examples::Echo> request) {
              binding_->Bind(std::move(request));
            };
        context_->outgoing()->AddPublicService(std::move(handler));
      }
    
     private:
      EchoImpl impl_;
      std::unique_ptr<fidl::Binding<fuchsia::examples::Echo>> binding_;
      std::unique_ptr<sys::ComponentContext> context_;
    };
    

    This is similar to the code that is explained in the server tutorial, but the fidl::Binding is owned by the class. This makes the binding's destructor get called when the class is destroyed. This enables the code to publish the echo protocol on each test case given a new instance of the test component context.

Implement the test fixture class

class EchoTestFixture : public gtest::TestLoopFixture {
 public:
  void SetUp() override {
    TestLoopFixture::SetUp();
    echo_instance_.reset(new EchoServerInstance(provider_.TakeContext()));
  }

  void TearDown() override {
    TestLoopFixture::TearDown();
    echo_instance_.reset();
  }

 protected:
  fuchsia::examples::EchoPtr GetProxy() {
    fuchsia::examples::EchoPtr echo;
    provider_.ConnectToPublicService(echo.NewRequest());
    return echo;
  }

 private:
  std::unique_ptr<EchoServerInstance> echo_instance_;
  sys::testing::ComponentContextProvider provider_;
};

The test fixture does the following:

  • Holds an instance of a ComponentContextProvider. Each test, it uses it to create a new test context, and binds the Echo implementation to it using the EchoServerInstance class.
  • Provides a GetProxy() method initializes a proxy to the current test component context and returns it.

Add tests

This is an example test that can you can write with the test fixture:

TEST_F(EchoTestFixture, EchoString) {
  fuchsia::examples::EchoPtr proxy = GetProxy();
  bool received_response = false;
  proxy->EchoString("hello there", [&](std::string response) {
    ASSERT_EQ(response, "hello there");
    received_response = true;
  });
  proxy.set_error_handler(
      [](zx_status_t status) { ASSERT_TRUE(false && "should not throw any errors"); });
  RunLoopUntilIdle();
  EXPECT_TRUE(received_response);
}

Run the test

To run the test:

  1. Configure your GN build to include the test:

    fx set core.x64 --with //examples/fidl/hlcpp/testing
    
  2. Run the test:

    fx test -vo example-hlcpp-protocol-test
    

You should see the test output indicating a success.

Summary

  • The gtest::TestLoopFixture removes the need for boilerplate async loop setup code. Each test case can simply call RunLoopUntilIdle() instead of manually managing an async::Loop.
  • The ComponentContextProvider makes it easy to mock the component context during a test. This is useful to e.g. provide specific capabilities to the a component.
  • The HLCPP bindings test scaffolding provides a test base for each protocol class that has a placeholder implementation for each method. This allows tests to only implement the methods under test.