Testing Strategy
Testing the React Compiler ensures that code transformations remain correct, memoization is applied optimally, and developer-facing validations trigger appropriately. The primary mechanism for testing the compiler is a fixture-based system that compares input source code against the compiler's output and runtime behavior.
Fixture-Based Testing
The compiler uses a "golden file" testing strategy. For every test case, the system tracks the input code, the transformed output, and the runtime results.
Creating a New Test Fixture
Test fixtures are stored in the __tests__/fixtures/compiler directory. To add a new test, create a .js or .ts file that exports a component or function and a FIXTURE_ENTRYPOINT configuration object.
// compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/example-test.js
function Component(props) {
const x = [];
x.push(props.value);
return x;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
isComponent: true,
};
The FIXTURE_ENTRYPOINT API
The FIXTURE_ENTRYPOINT object defines how the test runner should execute and validate the code:
| Property | Type | Description |
| :--- | :--- | :--- |
| fn | Function | The main function or component to be compiled and tested. |
| params | Array<any> | The arguments passed to fn during execution. |
| isComponent | boolean | Whether the function should be treated as a React component (enabling component-specific optimizations and rules). |
| sequentialRenders | Array<any> | (Optional) An array of different sets of params to run in sequence, used to verify that memoization persists or invalidates correctly across renders. |
Compiler Directives
You can configure specific compiler behaviors for a single test case using comment-based directives at the top of the file:
// @validateNoCapitalizedCalls: Enables validation that prevents calling capitalized functions (reserved for JSX components).// @compilationMode:"infer": Tells the compiler to infer whether a function is a component or hook.// @expectNothingCompiled: Asserts that the compiler should skip this file without making changes.// @validatePreserveExistingMemoizationGuarantees: Ensures the compiler respects manually optimized code (e.g.,useMemo).
Regression Testing
Fixtures serve as the end-to-end regression suite. When the test runner executes, it performs the following steps:
- Parsing: Converts the input string into an AST.
- Transformation: Runs the compiler passes (e.g.,
PruneNonEscapingScopes,InferMutationAliasing). - Code Generation: Produces the optimized JavaScript string.
- Execution: Runs both the original and compiled code in a VM to ensure the output value remains identical.
- Snapshotting: Compares the output against the existing
.expectfile.
Validating Errors
To test that the compiler correctly identifies invalid code, create a fixture that triggers a CompilerError. The test runner will capture the error message and location, ensuring the compiler provides helpful feedback for common mistakes like calling components as functions:
// @validateNoCapitalizedCalls
function Component() {
const x = SomeFunc(); // This will trigger an ErrorCategory.CapitalizedCalls
return x;
}
Running Tests
Tests are executed via jest. You can run the entire suite or filter by fixture name:
# Run all compiler tests
yarn jest packages/babel-plugin-react-compiler
# Run a specific fixture
yarn jest packages/babel-plugin-react-compiler -t 'fixture-name'
To update snapshots after making intentional changes to the compiler's output logic, use the -u flag:
yarn jest packages/babel-plugin-react-compiler -u
Best Practices
- Minimal Reproductions: Keep fixtures focused on a single optimization or validation rule.
- Traceable Dependencies: When testing memoization, use
sequentialRendersto verify that objects maintain reference equality when dependencies haven't changed. - TypeScript Support: Use
.tsor.tsxfixtures to ensure the compiler correctly handles type annotations and syntax without stripping necessary metadata.