Flipper Test Utilities
flipper-test-utils is a library providing common testing utilities specifically designed for developing and testing Flipper plugins and components within the Flipper ecosystem. It is an integral part of the larger Flipper monorepo maintained by Facebook, currently at version 0.273.0. The package has a rapid release cadence, with updates typically aligning with the main Flipper desktop application releases (often weekly or bi-weekly). Its primary purpose is to facilitate isolated and integrated testing of Flipper plugins by offering functionalities like component rendering (often re-exporting or wrapping `@testing-library/react` utilities), mocking the Flipper client API, and providing helper functions for common testing scenarios. This allows developers to thoroughly test their plugins' UI, data interactions, and Flipper host communications without needing a full Flipper desktop environment.
Common errors
-
Cannot find module 'flipper-test-utils' or its corresponding type declarations.
cause The package is not installed, or TypeScript cannot locate its declarations.fixRun `npm install flipper-test-utils` or `yarn add flipper-test-utils` to install it. Ensure your `tsconfig.json` includes `node_modules/@types` if using custom type roots. -
TypeError: (0 , _flipper_test_utils.render) is not a function
cause This usually indicates an issue with CommonJS (`require`) trying to import an ESM-style named export, or incorrect named vs. default import.fixEnsure you are using `import { render } from 'flipper-test-utils';` (ESM syntax) and that your test runner (e.g., Jest) is configured to handle ESM modules correctly. Avoid `const { render } = require('flipper-test-utils');` if the package primarily uses ESM named exports. -
Warning: An update to TestComponent inside a test was not wrapped in act(...).
cause A React component updated its state or props, causing a re-render outside of React's `act()` utility in your test. This can lead to unpredictable test behavior.fixWrap the code that causes state updates or re-renders (including the initial `render` call, user interactions, or awaiting async operations) within `await act(async () => { /* ... */ });`.
Warnings
- breaking As `flipper-test-utils` is deeply integrated into the Flipper monorepo, its APIs can experience breaking changes aligning with major or even minor Flipper desktop application updates. Referencing the main Flipper changelog is crucial for staying up-to-date, as this sub-package's dedicated changelog may not detail all related breaking changes.
- gotcha When testing React components that update state or use effects, ensure your test code that triggers these updates and assertions is wrapped in `act()`. Failure to do so can lead to `Act(...)` warnings and inconsistent test results, especially with React 18+.
- gotcha `flipper-test-utils` assumes certain peer dependencies like `jest`, `@testing-library/react`, and `react`. While not always explicitly listed as direct dependencies in `package.json`, their absence or incompatible versions can lead to runtime errors during testing.
Install
-
npm install flipper-test-utils -
yarn add flipper-test-utils -
pnpm add flipper-test-utils
Imports
- render
import { render } from '@testing-library/react';import { render } from 'flipper-test-utils'; - createStubFlipperLib
import { FlipperLib } from 'flipper-plugin'; // Then try to mock FlipperLib manuallyimport { createStubFlipperLib } from 'flipper-test-utils'; - TestUtils
import { TestUtils } from 'flipper-test-utils'; - act
import { act } from 'react-dom/test-utils';
Quickstart
import React from 'react';
import { render, createStubFlipperLib } from 'flipper-test-utils';
import { act } from 'react-dom/test-utils'; // Essential for React 18+ tests
import { FlipperPlugin, Flipper } from 'flipper-plugin';
interface MyPluginClient {
getGreeting(): Promise<string>;
}
// Define a simple Flipper plugin component
class MyFlipperPlugin extends FlipperPlugin<any, any, any> {
static id = 'MyFlipperPlugin';
static title = 'My Flipper Plugin';
constructor(flipper: Flipper) {
super(flipper);
}
render() {
return (
<div>
<h1>Hello from My Flipper Plugin!</h1>
<GreetingFetcher client={this.client} />
</div>
);
}
}
// A component that uses the Flipper client
function GreetingFetcher({ client }: { client: MyPluginClient }) {
const [greeting, setGreeting] = React.useState('Loading...');
React.useEffect(() => {
const fetchGreeting = async () => {
const message = await client.getGreeting();
setGreeting(message);
};
fetchGreeting();
}, [client]);
return <p>{greeting}</p>;
}
describe('MyFlipperPlugin', () => {
it('renders correctly and fetches a greeting', async () => {
const stubFlipperLib = createStubFlipperLib();
// Mock the client's method directly on the stub
stubFlipperLib.pluginClient.getGreeting = async () => 'Hello World!';
let rendered;
await act(async () => {
rendered = render(
<MyFlipperPlugin flipper={stubFlipperLib} />,
{ wrapper: ({ children }) => <div>{children}</div> } // Provide a simple wrapper if needed
);
});
expect(rendered.getByText('Hello from My Flipper Plugin!')).toBeInTheDocument();
// Wait for the async effect to complete
await rendered.findByText('Hello World!');
expect(rendered.getByText('Hello World!')).toBeInTheDocument();
});
});