Component tests

The Fuchsia Test Runner Framework enables developers to build tests for components using a variety of languages and runtimes and execute them on a target device. The framework provides test runner components that implement the fuchsia.test.Suite protocol and integrate with common language-specific testing frameworks such as GoogleTest (C++).

The test_manager component is responsible for running tests on a Fuchsia device. It examines components implementing the test suite protocol and launches them as child components. This means that test_manager is also responsible for providing capabilities to each test suite, creating what is commonly called the test realm.

Diagram showing how the Test Runner Framework provides interfaces for
developers to expose test suites and for developer tools to execute tests on
the Fuchsia device.

Developer tools such as ffx test communicate with the test_manager on the device to execute test suites and retrieve the results.

Test runners

Test runners are reusable adapters between the Test Runner Framework and common frameworks used by developers to write tests in their preferred language. Each test runner component exposes the fuchsia.test.Suite capability that enables the test_manager to enumerate and execute individual tests, and declares the appropriate execution runner with test framework support.

Rust

{
    // Execute tests using language-specific runner
    program: { runner: "rust_test_runner" },
    // Expose test suite protocol to test manager
    capabilities: [
        { protocol: "fuchsia.test.Suite" },
    ],
    expose: [
        {
            protocol: "fuchsia.test.Suite",
            from: "self",
        },
    ],
}

C++

{
    // Execute tests using language-specific runner
    program: { runner: "gtest_runner" },
    // Expose test suite protocol to test manager
    capabilities: [
        { protocol: "fuchsia.test.Suite" },
    ],
    expose: [
        {
            protocol: "fuchsia.test.Suite",
            from: "self",
        },
    ],
}

To simplify integration, the Test Runner Framework provides manifest shards for each language-specific runner. The following is an equivalent test runner CML for declaring the capabilities from the previous example component tests.

Rust

{
    include: [ "//src/sys/test_runners/rust/default.shard.cml" ]
}

C++

{
    include: [ "//src/sys/test_runners/gtest/default.shard.cml" ]
}

Unit tests

Unit testing focuses on validating the individual units of code within your component and isolated from other components on the system. Unit tests should be hermetic, meaning that they do not require or provide additional capabilities outside of the test.

The Fuchsia build system provides additional GN targets to facilitate unit testing components:

  • fuchsia_unittest_package(): A single-component package that automatically generates a minimal component manifest to reference the test binary and requires no additional capabilities.
  • fuchsia_unittest_component(): A component declaration that generates the same minimal component manifest. This rule is useful if you need to build multiple unit test components into the same fuchsia_package().

Below is an example BUILD.gn snippet for including unit tests:

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

executable("bin_test") {
  sources = [ "main_test.cc" ]
  deps = [
    "//src/lib/fxl/test:gtest_main",
    "//third_party/googletest:gtest",
  ]
  testonly = true
}

fuchsia_unittest_package("hello-world-unittests") {
  deps = [
    ":bin_test",
  ]
}

Exercise: Echo unit tests

In this exercise, you'll add unit tests to the echo-args component with the Test Runner Framework and run those tests in a FEMU environment.

Implement unit tests

Unit tests verify that the internal functions of the component behave as expected. For the echo-args component, you'll validate that the greeting() function used in the previous exercise returns the expected values.

Add the following unit test functions to validate the behavior of the greeting() function when supplied with one, two, or three arguments:

Rust

echo-args/src/main.rs:

#[cfg(test)]
mod tests {
    #[fuchsia::test]
    async fn test_greet_one() {
        let names = vec![String::from("Alice")];
        let expected = "Alice";
        assert_eq!(super::greeting(&names), expected);
    }

    #[fuchsia::test]
    async fn test_greet_two() {
        let names = vec![String::from("Alice"), String::from("Bob")];
        let expected = "Alice and Bob";
        assert_eq!(super::greeting(&names), expected);
    }

    #[fuchsia::test]
    async fn test_greet_three() {
        let names = vec![String::from("Alice"), String::from("Bob"), String::from("Spot")];
        let expected = "Alice, Bob, Spot";
        assert_eq!(super::greeting(&names), expected);
    }
}

C++

echo-args/echo_unittest.cc:

#include <gtest/gtest.h>

#include "vendor/fuchsia-codelab/echo-args/echo_component.h"

TEST(EchoTest, TestGreetOne) {
  std::vector<std::string> names = {"Alice"};
  std::string expected = "Alice";
  ASSERT_TRUE(echo::greeting(names) == expected);
}

TEST(EchoTest, TestGreetTwo) {
  std::vector<std::string> names = {"Alice", "Bob"};
  std::string expected = "Alice and Bob";
  ASSERT_TRUE(echo::greeting(names) == expected);
}

TEST(EchoTest, TestGreetThree) {
  std::vector<std::string> names = {"Alice", "Bob", "Spot"};
  std::string expected = "Alice, Bob, Spot";
  ASSERT_TRUE(echo::greeting(names) == expected);
}

Update the build configuration

Add the following rules to your BUILD.gn file to generate a new unit test package:

Rust

echo-args/BUILD.gn:

group("tests") {
  testonly = true
  deps = [ ":echo-args-unittests" ]
}

fuchsia_unittest_package("echo-args-unittests") {
  deps = [ ":bin_test" ]
}

C++

echo-args/BUILD.gn:

group("tests") {
  testonly = true
  deps = [ ":echo-args-unittests" ]
}

executable("unittests") {
  output_name = "echo-args-test"
  testonly = true

  sources = [ "echo_unittest.cc" ]

  deps = [
    ":cpp-lib",
    "//src/lib/fxl/test:gtest_main",
    "//third_party/googletest:gtest",
  ]
}

fuchsia_unittest_package("echo-args-unittests") {
  deps = [ ":unittests" ]
}

This rule packages your unit tests into a component with the following URL:

fuchsia-pkg://fuchsia.com/echo-args-unittests#meta/echo-args-unittests.cm

Run the unit tests

Update the top-level build target to build both your component package and the test package:

Rust

echo-args/BUILD.gn:

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


group("echo-args") {
  testonly = true
  deps = [
    ":package",
    ":tests",
  ]
}

C++

echo-args/BUILD.gn:

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


group("echo-args") {
  testonly = true
  deps = [
    ":package",
    ":tests",
  ]
}

Run fx build again to build the test package:

fx build

Use the ffx test command to execute the unit tests inside this package. Verify that the tests pass:

ffx test run \
    fuchsia-pkg://fuchsia.com/echo-args-unittests#meta/echo-args-unittests.cm