Testing in WebRTC

This document collects advice and best practices for writing tests in WebRTC, covering GN macros, GoogleTest/GMock usage, and available utilities.

Configure GN Macros

Use rtc_cc_test for new unit tests

As part of the “Small Tests” infrastructure, use rtc_cc_test for new unit tests. It defines a library and a standalone binary (prefixed with the folder name and suffixed with _bin). To generate the binary, run autoninja with a “_bin” suffix to the target - autoninja -C out/Default pc:proxy_unittest_bin will generate “out/Default/pc_proxy_unittest_bin” as your binary.

It should normally have a single source file.

Use rtc_test_suite to aggregate rtc_cc_test targets into larger “mega-targets” for CI/CQ (e.g., peerconnection_unittests). Each rtc_cc_test targets must be in exactly one rtc_test_suite target.

Refer to [add-new-test-binary.md](add-new-test-binary.md) for details on how to add the binary for a new test suite to the infrastructure.

Legacy: Using rtc_test to define test binaries

The rtc_test template is the traditional way to define test binaries in WebRTC. It automatically adds //test:test_main as a dependency, providing the standard main() function for running GoogleTest.

  • Example:

    import("//webrtc.gni")
    
    rtc_test("my_unittest") {
      sources = [ "my_unittest.cc" ]
      deps = [
        ":my_library",
        "//test:test_support",
      ]
    }
    

    New test suites should be defined using rtc_test_suite and rtc_cc_test.

Prefer rtc_library for test support

rtc_library is preferred for most targets (including test support code). It automatically switches between source_set and static_library based on configuration and the testonly flag.

Follow GoogleTest and GMock Best Practices

These are documented in the Google testing blog.

Refer to the GMock Matchers Reference for a full list of available matchers.

In WebRTC, include wrappers instead of raw headers

Always include test/gtest.h and test/gmock.h instead of the raw GoogleTest/GMock headers. These wrappers handle warning suppressions and exports correctly for WebRTC.

Use EXPECT_TRUE and EXPECT_FALSE only for booleans

Use these macros for pure boolean comparisons only. Do not rely on implicit conversions (e.g., checking if a pointer is non-null).

EXPECT_TRUE(pointer); // BAD: implicit conversion to bool
EXPECT_TRUE(integer_value); // BAD: implicit conversion
using ::testing::NotNull;

EXPECT_NE(pointer, nullptr);
EXPECT_THAT(pointer, NotNull());
EXPECT_NE(integer_value, 0);

See the relevant Abseil tips for details.

Note that using at the beginning of the file is recommended in gmock.

Prefer EXPECT_THAT for complex evaluations

EXPECT_EQ, EXPECT_LE, EXPECT_LT, EXPECT_GE, EXPECT_GT are acceptable for simple cases. Prefer EXPECT_THAT and matchers for anything with complex evaluations or when better error messages are needed.

EXPECT_TRUE(vec.size() == 1 && vec[0] == "val"); // poor failure message
using ::testing::ElementsAre;

EXPECT_THAT(vec, ElementsAre("val"));
EXPECT_LE(value, 10); // okay for simple cases

Avoid Yoda matching

Always put the tested value first and the constant/expected value last in all expectations.

EXPECT_EQ(0, value);
EXPECT_EQ(value, 0);

Use ASSERT_ for fatal conditions

Prefer EXPECT_ for the normal case. This allows you to report several errors from one single test run.

Use ASSERT_ if failing the condition would cause a crash or undefined behavior in subsequent code, or if failing the check would make later tests irrelevant (e.g., checking if a pointer is null before dereferencing it).

This is also recommended by the GoogleTest primer.

Avoid expectations in helper functions

Helper functions in tests that contain expectations (EXPECT_/ASSERT_) are discouraged. Instead, use matchers to make tests more readable and provide better failure messages. You can create custom matchers in three ways:

  1. Returning a Matcher Combination: Combine existing matchers.
    auto FirstElementIs(int x) {
      return ::testing::AllOf(
          ::testing::Not(::testing::IsEmpty()),
          ::testing::ResultOf([](const auto& v) { return v.front(); }, ::testing::Eq(x)));
    }
    
  2. Using MATCHER_P Macros: For simple parameterized matchers.
  3. Implementing a Matcher Class: For complex matchers that need to provide detailed explanations.
    • Example in test/near_matcher.h which implements a NearMatcher for time types.
    • Usage Example:
      #include "test/near_matcher.h"
      // ...
      EXPECT_THAT(actual_timestamp, webrtc::Near(expected_timestamp, TimeDelta::Millis(5)));
      

More information about writing matchers is found in the GMock cookbook.

Use WebRTC Testing Utilities

Use GlobalSimulatedTimeController for simulated time

Use GlobalSimulatedTimeController to run tests that depend on time (e.g., pacing, timeouts) without waiting for real time to pass. It provides a Clock and TaskQueueFactory. Prefer modern simulated time infrastructure over legacy ScopedFakeClock.

Create an Environment using CreateTestEnvironment

The Environment is the modern way to propagate global utilities (like Clock, TaskQueueFactory, FieldTrials) through the codebase. Tests that exercise any component using an Environment must always create an Environment using webrtc::CreateTestEnvironment (defined in test/create_test_environment.h) and pass it to the components being tested.

Using CreateEnvironment will cause the test to ignore the command line force-fieldtrials flag, so this should not be used in tests unless there are specific reasons for using it.

Avoid polling, use WaitUntil when needed

webrtc::WaitUntil (located in test/wait_until.h) is a modern utility for waiting for a condition to become true. It is best to avoid polling where possible, but when needed, use WaitUntil instead of manual polling or sleeping. It polls a predicate using the TimeController.

Use RunLoop for single-threaded async simulation

webrtc::test::RunLoop (located in test/run_loop.h) is a helper class for tests that need to process tasks posted to a task queue but still want to run everything on a single thread. It is useful for simulating async operations simply.

Select a Scenario Framework

Some of the available frameworks:

  • Scenario (test/scenario): Best for network and media quality evaluation (e.g., bandwidth estimation, congestion control).
  • PeerScenario (test/peer_scenario): Best for signaling and PeerConnection API level tests that require multiple threads and a simulated network but want to stay lightweight. **PCLF** (api/test/pclf`) is a framework for full integration tests.
  • **IntegrationTestHelpers (pc/test/integration_test_helpers) is an older set of tools that is heavily used for PeerConnection-related unit testing.

Other frameworks for more specialized purposes also exist. <--! Question: Should we recommend one over the others? -->

Mocking

A lot of classes have existing mocks. Mocks for classes used in api live in api/test; there are some in test, but in general, mocks for internal classes should live close to the class they mock.

Mocks should be “pure mocks” using gmock. If the test double needs to have code in it, it should be called a fake. See this testing blog for terminology.

General advice on mocking is in the GMock cookbook.

Leverage Other Utilities

  • FrameGenerator: Located in test/frame_generator.h; it is useful for generating video frames for testing video pipelines.

Run Tests

Use gtest-parallel for faster local execution

For faster local execution, use third_party/gtest-parallel/gtest-parallel <binary>.

Note that some tests are timing dependent and may be flaky when run under gtest-parallel.