Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

4466 articles

UI testing


UI testing

Test your Stripe app UI with a set of utilities and helpers.

The Extension SDK includes a set of tools to write unit tests for your app’s user interface. We recommend running tests with Jest and we include Jest custom matchers to help with writing assertions.

Conceptual overview

When testing your Stripe app’s UI, you’re testing a remote engine that renders your app, not the Document Object Model (DOM) tree directly.

For security purposes, the React code in your Stripe app repository is serialized, sent through an extension loader using an iframe, and translated into a DOM tree within the Stripe Dashboard. The testing tools provided by the Extension SDK work with the remote rendering engine.

Example

This example tests a Button UI component that changes text when clicked. In the test, we render the button, confirm that the initial button text is correct, click the button, and confirm that the text of the button has changed.

Rendering a component

render(element: React.ReactElement)

The render method accepts a React element and returns an object with the following properties:

  • wrapper : The root element of the component passed to render .
  • update : A function that returns a promise that resolves after the JavaScript event stack has been cleared. This is useful when mocking APIs, dealing with promises, employing React hooks such as useEffect , or ensuring asynchronous rendering completes before running subsequent test cases.

Element properties and methods

When working with the wrapper or any element within it, use the following properties and methods to assess state and interact with your app:

children: Element<unknown>[]

Returns an array of the direct children of the element.

descendants: Element<unknown>[]

Returns an array of all elements below the element in the tree.

debug(options?: {all?: boolean, depth?: number, verbosity?: number}): string

Returns a text representation of the element. You can modify debug() output using the options parameter.

  • all overrides the default props filtering behavior and instead includes all props in the output. debug() omits className , aria-* , and data-* props by default.
  • depth defines the number of children printed. All children are printed by default.
  • verbosity defines the level of expansion for non-scalar props. The default value of 1 expands objects one level deep.

act<T>(action: () => T): T

Performs an action in the context of a React act() block. Normally, you can use update() (which uses act() internally) to handle asynchronous events. However, in some cases you might need to call act() directly, such as when your code uses timers ( setTimeout, setInterval, clearTimeout, clearInterval), and you want to test using timer mocks. When using timer mocks, you need to reset or cleanup mocks between tests (in jest this means calling runOnlyPendingTimers() and useRealTimers()), otherwise library code that uses timers won’t work properly.

find(type: Type, props?: Partial<PropsForComponent<Type>>): Element<PropsForComponent<Type>> | null

Finds a descendant element that matches type, where type is a component. If it doesn’t find a matching element, it returns null. If it finds a match, the returned element has the correct prop typing, which provides excellent type safety while navigating the React tree.

If the second props argument is passed, it finds the first element of type with matching props.

Be aware that when using any of the findX methods, saved results are immediately stale and future updates to the component aren’t reflected. For example:

// Bad - this will not work
const button = wrapper.find(Button);
expect(button).toContainText('Press me');
button!.trigger('onPress');
expect(button).toContainText('You pressed me!'); // button still contains 'Press me'

// Good - this will work
expect(wrapper.find(Button)).toContainText('Press me');
wrapper.find(Button)!.trigger('onPress');
expect(wrapper.find(Button)).toContainText('You pressed me!');

findAll(type: Type, props?: Partial<PropsForComponent<Type>>): Element<PropsForComponent<Type>>[]

Like find, but returns all matches as an array.

findWhere<Type = unknown>(predicate: (element: Element<unknown>) => boolean): Element<PropsForComponent<Type>> | null

Finds the first descendant component matching the passed function. The function is called with each element from descendants until it finds a match. If it doesn’t find a match, it returns null.

findWhere accepts an optional TypeScript argument that you can use to specify the type of the returned element. If you omit the generic argument, the returned element has unknown props, so calling .props and .trigger on it causes type errors, as those functions don’t know what props are valid on your element:

findAllWhere<Type = unknown>(predicate: (element: Element<unknown>) => boolean): Element<PropsForComponent<Type>>[]

Like findWhere, but returns all matches as an array.

is(type: Type): boolean

Returns a boolean indicating whether the component type matches the passed type. This function also serves as a type guard, so subsequent calls to values like props are typed as the prop type of the passed component.

prop<K extends keyof Props>(key: K): Props[K]

Returns the current value of the passed prop name.

props: Props

All props of the element.

text: string

The text content of the element (that is, the string you would get by calling textContent).

trigger<K extends FunctionKeys<Props>>(prop: K, …args: Arguments<Props<K>>): ReturnType<Props<K>>

Simulates a function prop being called on your component. This is usually the key to effective testing. After you mount your component, you simulate a change in a subcomponent and assert that the resulting tree is in the expected state.

Optionally, each additional argument passed to trigger is passed to the function. This is useful for testing components in isolation.

triggerKeypath<T>(keypath: string, …args: any[]): T

Like trigger(), but allows you to provide a keypath referencing nested objects. Be aware that limitations in TypeScript prevent the same kind of type-safety that trigger guarantees.

const App = ({action}: {action: {onAction(): void; label: string}}) => (
 <Button type="button" onPress={action.onAction}>
 {action.label}
 </Button>
);

const spy = jest.fn();
const app = mount(
 <App action={{label: 'Hi', onAction: spy}} />,
);
app.triggerKeypath('action.onAction');
expect(spy).toHaveBeenCalled();

Matchers

The Extension SDK provides Jest custom matchers. These are imported automatically when you import @the relevant part of the product.

toContainComponent(type: RemoteComponentType, props?: object)

Asserts that at least one component matching type is in the descendants of the passed node. If the second props argument is passed, it further filters the matches by components whose props are equal to the passed object. Jest’s asymmetric matchers, like expect.objectContaining, are fully supported.

toContainComponentTimes(type: RemoteComponentType, times: number, props?: object)

Identical to .toContainComponent, but asserts that there are exactly times matches within the passed node.

toHaveProps(props: object)

Checks whether the node has the specified props.

toContainText(text: string)

Checks that the rendered output of the component contains the passed string as text content (that is, the text is included in what you would get by calling textContent on all DOM nodes rendered by the component).

Mock context props

App views are passed context props in the Stripe Dashboard. You can generate a mock context props object for testing purposes using the getMockContextProps function.

By default, the mock context props are standard test values like id: 'usr_1234' and email: ''. You can override these values by passing in a partial object. The object you pass in is deep-merged with the default object, so you only need to pass in the values you want to override.

See also

Last verified 2026-09-24

Is this helpful?