Integration testing

Integration testing focuses on validating your component's behavior as it interacts with other components on the system. Because of this, integration tests are typically built separately from the main component and may declare the component under test and other dependencies as children. Depending on the nature of the test, dependency components may be provided as mocks or stubs to promote that the test cases remain hermetic.

Test components

Below is an example component manifest for a simple integration test component:

Rust

meta/integration_tests.cml:

{
    include: [
        "syslog/client.shard.cml",
        "//src/sys/test_runners/rust/default.shard.cml",
    ],
    program: {
        binary: "bin/client_test",
    },
    children: [
        {
            name: "service",
            url: "fuchsia-pkg://fuchsia.com/foo-package-tests#meta/mock_service.cm",
        },
        {
            name: "client",
            url: "fuchsia-pkg://fuchsia.com/foo-package-tests#meta/foo_client.cm",
        },
    ],
    offer: [
        {
            protocol: "fuchsia.example.Foo",
            from: "#service",
            to: [ "#client" ],
        },
    ],
}

C++

meta/integration_tests.cml:

{
    include: [
        "syslog/client.shard.cml",
        "//src/sys/test_runners/gtest/default.shard.cml",
    ],
    program: {
        binary: "bin/client_test",
    },
    children: [
        {
            name: "service",
            url: "fuchsia-pkg://fuchsia.com/foo-package-tests#meta/mock_service.cm",
        },
        {
            name: "client",
            url: "fuchsia-pkg://fuchsia.com/foo-package-tests#meta/foo_client.cm",
        },
    ],
    offer: [
        {
            protocol: "fuchsia.example.Foo",
            from: "#service",
            to: [ "#client" ],
        },
    ],
}

This test component declaration contains the following key elements:

  1. An include of the necessary language-specific test runner shard. This enables the test manager to properly execute the test suite.
  2. Listing the component under test and dependent components as children.
  3. Routing required capabilities between components in the test realm.

The Fuchsia build system provides the fuchsia_test_package() GN target for distinct test components such as integration tests. This rule enables you to declare the components containing tests separately from those required as dependencies, and describes the target device environment where the tests should run.

Here is an example of how the above integration test could be included in the BUILD.gn file:

import("//build/components.gni")
...

// Component under test
fuchsia_component("foo_client") {
  deps = [ ... ]
  manifest = "meta/foo_client.cml"
}

// Test dependencies
fuchsia_component("mock_service") {
  deps = [ ... ]
  manifest = "meta/mock_service.cml"
  testonly = true
}

// Component containing integration tests
fuchsia_component("integration_tests") {
  deps = [ ":bin_test" ]
  manifest = "meta/integration_tests.cml"
  testonly = true
}

fuchsia_test_package("hello-world-tests") {
  test_components = [ ":integration_tests" ]
  deps = [
    ":foo_client",
    ":mock_service",
  ]
}

Exercise: Echo server integration test

In this exercise, you'll add an integration test component to exercise the FIDL protocol interface of the echo_server component with the Test Runner Framework and run those tests in a FEMU environment.

Add an integration test component

To begin, create a project scaffold for a new integration test component called echo-integration in the //vendor/fuchsia-codelab directory:

mkdir -p vendor/fuchsia-codelab/echo-integration

Create the following file and directory structure in the new project directory:

Rust

echo-integration
  |- BUILD.gn
  |- meta
  |   |- echo_integration.cml
  |
  |- src
      |- lib.rs
  • BUILD.gn: GN build targets for the test binaries, component, and package.
  • meta/echo_integration.cml: Manifest declaring the components under test and their capabilities.
  • src/lib.rs: Source code for the Rust integration tests.

C++

echo-integration
  |- BUILD.gn
  |- meta
  |   |- echo_integration.cml
  |
  |- echo_integration_test.cc
  • BUILD.gn: GN build targets for the test binaries, component, and package.
  • meta/echo_integration.cml: Manifest declaring the components under test and their capabilities.
  • echo_integration_test.cc: Source code for the C++ integration tests.

Update the test component manifest

The manifest for the test component applies the baseline dependencies, such as test_runners. Update the echo_integration.cml file to declare the echo-server component as a child and route the Echo protocol capability to the test component.

Rust

echo-integration/meta/echo_integration.cml:

{
    include: [
        "//src/sys/test_runners/rust/default.shard.cml",
        "syslog/client.shard.cml",
    ],

    // Information about the program to run.
    program: {
        // The binary to run for this component.
        binary: "bin/echo_integration_test",
    },


    // Child components orchestrated by the integration test.
    children: [
        {
            name: "echo_server",
            url: "#meta/echo_server.cm",
        },
    ],

    // Capabilities used by this component.
    use: [
        {
            protocol: [ "fidl.examples.routing.echo.Echo" ],
            from: "#echo_server",
        },
    ],

    // Capabilities required by components under test.
    offer: [
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: "#echo_server",
        },
    ],
}

C++

echo-integration/meta/echo_integration.cml:

{
    include: [
        "//src/sys/test_runners/gtest/default.shard.cml",
        "syslog/client.shard.cml",
    ],

    // Information about the program to run.
    program: {
        // The binary to run for this component.
        binary: "bin/echo_integration_test",
    },


    // Child components orchestrated by the integration test.
    children: [
        {
            name: "echo_server",
            url: "#meta/echo_server.cm",
        },
    ],

    // Capabilities used by this component.
    use: [
        {
            protocol: [ "fidl.examples.routing.echo.Echo" ],
            from: "#echo_server",
        },
    ],

    // Capabilities required by components under test.
    offer: [
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: "#echo_server",
        },
    ],
}

Notice that the echo-server instance comes from the same package as the integration test. This practice promotes test packages that are hermetic by avoiding dependencies on components from other packages.

Implement the integration test

The integration test connects to the Echo protocol exposed by the echo-server in the same way as the client component, sends a string request, and validates the expected response.

Add the following code to implement an integration test:

Rust

echo-integration/src/lib.rs:

use {anyhow::Error, fidl_fidl_examples_routing_echo as fecho, fuchsia_component::client};

#[fuchsia::test]
async fn echo_integration_test() -> Result<(), Error> {
    const ECHO_STRING: &str = "Hello, world!";

    let echo = client::connect_to_protocol::<fecho::EchoMarker>()
        .expect("error connecting to echo server");
    let out = echo.echo_string(Some(ECHO_STRING)).await.expect("echo_string failed");

    assert_eq!(ECHO_STRING, out.unwrap());
    Ok(())
}

C++

echo-integration/echo_integration_test.cc:

#include <fidl/examples/routing/echo/cpp/fidl.h>
#include <lib/fidl/cpp/string.h>
#include <lib/sys/cpp/component_context.h>

#include <string>

#include <gtest/gtest.h>

TEST(EchoIntegrationTest, TestEcho) {
  ::fidl::examples::routing::echo::EchoSyncPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  ::fidl::StringPtr request("Hello, world!");
  ::fidl::StringPtr response = nullptr;
  ASSERT_TRUE(echo_proxy->EchoString(request, &response) == ZX_OK);
  ASSERT_TRUE(request == response);
}

Add the following build rules to your BUILD.gn file to build and package the integration test component along with the echo server dependencies:

Rust

echo-integration/BUILD.gn:

import("//build/components.gni")
import("//build/rust/rustc_test.gni")


rustc_test("bin") {
  name = "echo-integration-test"
  edition = "2021"

  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo_rust",
    "//src/lib/fuchsia",
    "//src/lib/fuchsia-component",
    "//third_party/rust_crates:anyhow",
  ]

  sources = [ "src/lib.rs" ]
}

fuchsia_component("component") {
  testonly = true
  component_name = "echo_integration"
  manifest = "meta/echo_integration.cml"
  deps = [ ":bin" ]
}

fuchsia_test_package("tests") {
  package_name = "echo-integration-tests"
  test_components = [ ":component" ]
  deps =
      [ "//vendor/fuchsia-codelab/echo-server:component" ]
}

C++

echo-integration/BUILD.gn:

import("//build/components.gni")


executable("bin") {
  output_name = "echo-integration-test"
  sources = [ "echo_integration_test.cc" ]
  deps = [
    "//vendor/fuchsia-codelab/echo-fidl:echo_hlcpp",
    "//sdk/lib/sys/cpp",
    "//sdk/lib/sys/cpp/testing:unit",
    "//src/lib/fxl/test:gtest_main",
    "//third_party/googletest:gtest",
    "//zircon/system/ulib/async-loop:async-loop-cpp",
    "//zircon/system/ulib/async-loop:async-loop-default",
  ]
  testonly = true
}

fuchsia_component("component") {
  testonly = true
  component_name = "echo_integration"
  manifest = "meta/echo_integration.cml"
  deps = [ ":bin" ]
}

fuchsia_test_package("tests") {
  package_name = "echo-integration-tests"
  test_components = [ ":component" ]
  deps =
      [ "//vendor/fuchsia-codelab/echo-server:component" ]
}

Update the build configuration

Add the integration test package to the build configuration:

fx set workstation_eng.x64 \
    --with //vendor/fuchsia-codelab/echo-server \
    --with //vendor/fuchsia-codelab/echo-client \
    --with //vendor/fuchsia-codelab/echo-realm \
    --with //vendor/fuchsia-codelab/echo-integration:tests

Run fx build and verify that the build completes successfully:

fx build

Run the integration test

The fuchsia_test_package() rule generates a package with the test component and its dependencies. The integration test component has the following URL:

fuchsia-pkg://fuchsia.com/echo-integration-tests#meta/echo_integration.cm

Use the ffx test command to execute the integration tests. Verify that the tests pass:

ffx test run \
    fuchsia-pkg://fuchsia.com/echo-integration-tests#meta/echo_integration.cm