Simple and complete React Native testing utilities that encourage good testing practices
—
Extended Jest matchers specifically designed for React Native component testing and assertions. These matchers integrate with Jest's expect API to provide intuitive and readable assertions for React Native elements.
Matchers for testing element visibility and presence in the component tree.
/**
* Assert element is currently on the screen (in the rendered tree)
*/
expect(element).toBeOnTheScreen(): void;
expect(element).not.toBeOnTheScreen(): void;
/**
* Assert element is visible to users (considering accessibility and style)
*/
expect(element).toBeVisible(): void;
expect(element).not.toBeVisible(): void;
/**
* Assert element is empty (has no children)
*/
expect(element).toBeEmptyElement(): void;
expect(element).not.toBeEmptyElement(): void;Usage Examples:
import { render, screen } from "@testing-library/react-native";
test("visibility matchers", () => {
render(
<View>
<Text testID="visible-text">Visible text</Text>
<View testID="empty-container" />
<Text
testID="hidden-text"
style={{ opacity: 0 }}
>
Hidden text
</Text>
<View testID="accessibility-hidden" accessibilityElementsHidden>
<Text>Hidden from accessibility</Text>
</View>
</View>
);
const visibleText = screen.getByTestId("visible-text");
const emptyContainer = screen.getByTestId("empty-container");
const hiddenText = screen.getByTestId("hidden-text");
const a11yHidden = screen.getByTestId("accessibility-hidden");
// Element presence
expect(visibleText).toBeOnTheScreen();
expect(hiddenText).toBeOnTheScreen(); // Still in tree, just not visible
// Element visibility
expect(visibleText).toBeVisible();
expect(hiddenText).not.toBeVisible(); // Has opacity: 0
expect(a11yHidden).not.toBeVisible(); // Hidden from accessibility
// Empty elements
expect(emptyContainer).toBeEmptyElement();
expect(visibleText).not.toBeEmptyElement(); // Has text content
});
test("conditional rendering", () => {
const ConditionalComponent = ({ show }) => (
<View>
{show && <Text testID="conditional">Conditional text</Text>}
</View>
);
const { rerender } = render(<ConditionalComponent show={false} />);
// Element not present when show=false
expect(screen.queryByTestId("conditional")).not.toBeOnTheScreen();
rerender(<ConditionalComponent show={true} />);
// Element present when show=true
expect(screen.getByTestId("conditional")).toBeOnTheScreen();
expect(screen.getByTestId("conditional")).toBeVisible();
});Matchers for testing interactive element states and accessibility properties.
/**
* Assert element is disabled/enabled
*/
expect(element).toBeDisabled(): void;
expect(element).not.toBeDisabled(): void;
expect(element).toBeEnabled(): void;
expect(element).not.toBeEnabled(): void;
/**
* Assert element is busy (has accessibilityState.busy = true)
*/
expect(element).toBeBusy(): void;
expect(element).not.toBeBusy(): void;
/**
* Assert element is selected
*/
expect(element).toBeSelected(): void;
expect(element).not.toBeSelected(): void;
/**
* Assert element is expanded/collapsed
*/
expect(element).toBeExpanded(): void;
expect(element).not.toBeExpanded(): void;
expect(element).toBeCollapsed(): void;
expect(element).not.toBeCollapsed(): void;Usage Examples:
test("interactive state matchers", () => {
render(
<View>
<Pressable
testID="disabled-button"
disabled={true}
accessibilityState={{ disabled: true }}
>
<Text>Disabled Button</Text>
</Pressable>
<Pressable
testID="enabled-button"
disabled={false}
>
<Text>Enabled Button</Text>
</Pressable>
<View
testID="busy-indicator"
accessibilityState={{ busy: true }}
>
<Text>Loading...</Text>
</View>
<View
testID="selected-item"
accessibilityState={{ selected: true }}
>
<Text>Selected Item</Text>
</View>
<View
testID="expanded-section"
accessibilityState={{ expanded: true }}
>
<Text>Expanded Section</Text>
</View>
</View>
);
const disabledButton = screen.getByTestId("disabled-button");
const enabledButton = screen.getByTestId("enabled-button");
const busyIndicator = screen.getByTestId("busy-indicator");
const selectedItem = screen.getByTestId("selected-item");
const expandedSection = screen.getByTestId("expanded-section");
// Disabled/enabled state
expect(disabledButton).toBeDisabled();
expect(enabledButton).toBeEnabled();
expect(enabledButton).not.toBeDisabled();
// Busy state
expect(busyIndicator).toBeBusy();
expect(enabledButton).not.toBeBusy();
// Selected state
expect(selectedItem).toBeSelected();
expect(disabledButton).not.toBeSelected();
// Expanded/collapsed state
expect(expandedSection).toBeExpanded();
expect(expandedSection).not.toBeCollapsed();
});
test("form element states", () => {
render(
<View>
<TextInput
testID="disabled-input"
editable={false}
accessibilityState={{ disabled: true }}
/>
<Switch
testID="toggle-switch"
value={true}
accessibilityState={{ checked: true }}
/>
</View>
);
const disabledInput = screen.getByTestId("disabled-input");
const toggleSwitch = screen.getByTestId("toggle-switch");
expect(disabledInput).toBeDisabled();
expect(toggleSwitch).toBeChecked();
});Specialized matchers for checkbox-like components and selection states.
/**
* Assert element is checked (for checkboxes, switches, radio buttons)
*/
expect(element).toBeChecked(): void;
expect(element).not.toBeChecked(): void;
/**
* Assert element is partially checked (for indeterminate checkboxes)
*/
expect(element).toBePartiallyChecked(): void;
expect(element).not.toBePartiallyChecked(): void;Usage Examples:
test("checkbox matchers", () => {
const CheckboxComponent = ({ checked, indeterminate }) => (
<Pressable
testID="checkbox"
accessibilityRole="checkbox"
accessibilityState={{
checked: indeterminate ? "mixed" : checked
}}
>
<Text>{indeterminate ? "[-]" : checked ? "[×]" : "[ ]"}</Text>
</Pressable>
);
// Unchecked checkbox
const { rerender } = render(
<CheckboxComponent checked={false} indeterminate={false} />
);
const checkbox = screen.getByTestId("checkbox");
expect(checkbox).not.toBeChecked();
expect(checkbox).not.toBePartiallyChecked();
// Checked checkbox
rerender(<CheckboxComponent checked={true} indeterminate={false} />);
expect(checkbox).toBeChecked();
expect(checkbox).not.toBePartiallyChecked();
// Indeterminate checkbox
rerender(<CheckboxComponent checked={false} indeterminate={true} />);
expect(checkbox).toBePartiallyChecked();
expect(checkbox).not.toBeChecked();
});
test("switch component", () => {
render(
<Switch
testID="notification-switch"
value={true}
accessibilityLabel="Enable notifications"
accessibilityState={{ checked: true }}
/>
);
const notificationSwitch = screen.getByTestId("notification-switch");
expect(notificationSwitch).toBeChecked();
});Matchers for testing text content and display values.
/**
* Assert element has specific text content
* @param text - Expected text content (string or RegExp)
*/
expect(element).toHaveTextContent(text: string | RegExp): void;
expect(element).not.toHaveTextContent(text: string | RegExp): void;
/**
* Assert form element has specific display value
* @param value - Expected display value (string or RegExp)
*/
expect(element).toHaveDisplayValue(value: string | RegExp): void;
expect(element).not.toHaveDisplayValue(value: string | RegExp): void;Usage Examples:
test("text content matchers", () => {
render(
<View>
<Text testID="greeting">Hello, World!</Text>
<Text testID="user-name">John Doe</Text>
<Text testID="empty-text"></Text>
<View testID="container">
<Text>First line</Text>
<Text>Second line</Text>
</View>
</View>
);
const greeting = screen.getByTestId("greeting");
const userName = screen.getByTestId("user-name");
const emptyText = screen.getByTestId("empty-text");
const container = screen.getByTestId("container");
// Exact text content
expect(greeting).toHaveTextContent("Hello, World!");
expect(userName).toHaveTextContent("John Doe");
// RegExp matching
expect(greeting).toHaveTextContent(/hello.*world/i);
expect(userName).toHaveTextContent(/john/i);
// Empty content
expect(emptyText).toHaveTextContent("");
expect(emptyText).not.toHaveTextContent("anything");
// Container with multiple text elements
expect(container).toHaveTextContent("First line Second line");
expect(container).toHaveTextContent(/first.*second/i);
});
test("display value matchers", () => {
render(
<View>
<TextInput
testID="email-input"
value="user@example.com"
placeholder="Email"
/>
<TextInput
testID="empty-input"
value=""
placeholder="Empty input"
/>
<Text testID="display-text">Current value: 42</Text>
</View>
);
const emailInput = screen.getByTestId("email-input");
const emptyInput = screen.getByTestId("empty-input");
const displayText = screen.getByTestId("display-text");
// Input values
expect(emailInput).toHaveDisplayValue("user@example.com");
expect(emailInput).toHaveDisplayValue(/@example\.com$/);
expect(emptyInput).toHaveDisplayValue("");
// Text element values
expect(displayText).toHaveDisplayValue("Current value: 42");
expect(displayText).toHaveDisplayValue(/value: \d+/);
// Negative assertions
expect(emailInput).not.toHaveDisplayValue("wrong@email.com");
expect(emptyInput).not.toHaveDisplayValue("something");
});Matchers for testing accessibility properties and labels.
/**
* Assert element has specific accessible name
* @param name - Expected accessible name (string or RegExp)
*/
expect(element).toHaveAccessibleName(name: string | RegExp): void;
expect(element).not.toHaveAccessibleName(name: string | RegExp): void;
/**
* Assert element has specific accessibility value
* @param value - Expected accessibility value object
*/
expect(element).toHaveAccessibilityValue(value: {
min?: number;
max?: number;
now?: number;
text?: string;
}): void;
expect(element).not.toHaveAccessibilityValue(value: object): void;Usage Examples:
test("accessibility matchers", () => {
render(
<View>
<Pressable
testID="submit-button"
accessibilityLabel="Submit form"
accessibilityHint="Tap to submit the form"
>
<Text>Submit</Text>
</Pressable>
<Slider
testID="volume-slider"
minimumValue={0}
maximumValue={100}
value={75}
accessibilityLabel="Volume control"
accessibilityValue={{
min: 0,
max: 100,
now: 75,
text: "75 percent"
}}
/>
<TextInput
testID="name-input"
accessibilityLabel="Full name"
placeholder="Enter your full name"
/>
</View>
);
const submitButton = screen.getByTestId("submit-button");
const volumeSlider = screen.getByTestId("volume-slider");
const nameInput = screen.getByTestId("name-input");
// Accessible names
expect(submitButton).toHaveAccessibleName("Submit form");
expect(submitButton).toHaveAccessibleName(/submit/i);
expect(volumeSlider).toHaveAccessibleName("Volume control");
expect(nameInput).toHaveAccessibleName("Full name");
// Accessibility values
expect(volumeSlider).toHaveAccessibilityValue({
min: 0,
max: 100,
now: 75,
text: "75 percent"
});
// Partial accessibility value matching
expect(volumeSlider).toHaveAccessibilityValue({ now: 75 });
expect(volumeSlider).toHaveAccessibilityValue({ text: "75 percent" });
// Negative assertions
expect(submitButton).not.toHaveAccessibleName("Cancel");
expect(volumeSlider).not.toHaveAccessibilityValue({ now: 50 });
});
test("accessibility value updates", () => {
const ProgressComponent = ({ progress }) => (
<View
testID="progress-bar"
accessibilityRole="progressbar"
accessibilityValue={{
min: 0,
max: 100,
now: progress,
text: `${progress}% complete`
}}
/>
);
const { rerender } = render(<ProgressComponent progress={25} />);
const progressBar = screen.getByTestId("progress-bar");
// Initial progress
expect(progressBar).toHaveAccessibilityValue({
now: 25,
text: "25% complete"
});
// Updated progress
rerender(<ProgressComponent progress={75} />);
expect(progressBar).toHaveAccessibilityValue({
now: 75,
text: "75% complete"
});
});Matchers for testing component props and styles.
/**
* Assert element has specific prop with optional value
* @param prop - Prop name to check
* @param value - Optional expected prop value
*/
expect(element).toHaveProp(prop: string, value?: any): void;
expect(element).not.toHaveProp(prop: string, value?: any): void;
/**
* Assert element has specific styles
* @param styles - Expected style object
*/
expect(element).toHaveStyle(styles: object): void;
expect(element).not.toHaveStyle(styles: object): void;Usage Examples:
test("prop matchers", () => {
render(
<View>
<TextInput
testID="styled-input"
placeholder="Enter text"
multiline={true}
numberOfLines={4}
keyboardType="email-address"
autoCapitalize="none"
/>
<Pressable
testID="custom-button"
disabled={false}
onPress={() => {}}
customProp="custom-value"
>
<Text>Button</Text>
</Pressable>
</View>
);
const styledInput = screen.getByTestId("styled-input");
const customButton = screen.getByTestId("custom-button");
// Check prop existence
expect(styledInput).toHaveProp("placeholder");
expect(styledInput).toHaveProp("multiline");
expect(customButton).toHaveProp("onPress");
// Check prop values
expect(styledInput).toHaveProp("placeholder", "Enter text");
expect(styledInput).toHaveProp("multiline", true);
expect(styledInput).toHaveProp("numberOfLines", 4);
expect(styledInput).toHaveProp("keyboardType", "email-address");
expect(customButton).toHaveProp("disabled", false);
expect(customButton).toHaveProp("customProp", "custom-value");
// Negative assertions
expect(styledInput).not.toHaveProp("nonexistent");
expect(styledInput).not.toHaveProp("multiline", false);
expect(customButton).not.toHaveProp("disabled", true);
});
test("style matchers", () => {
render(
<View>
<View
testID="styled-view"
style={{
backgroundColor: "blue",
padding: 10,
margin: 5,
borderRadius: 8,
flexDirection: "row"
}}
/>
<Text
testID="styled-text"
style={[
{ fontSize: 16, color: "red" },
{ fontWeight: "bold" }
]}
>
Styled Text
</Text>
</View>
);
const styledView = screen.getByTestId("styled-view");
const styledText = screen.getByTestId("styled-text");
// Single style properties
expect(styledView).toHaveStyle({ backgroundColor: "blue" });
expect(styledView).toHaveStyle({ padding: 10 });
expect(styledText).toHaveStyle({ fontSize: 16 });
expect(styledText).toHaveStyle({ color: "red" });
// Multiple style properties
expect(styledView).toHaveStyle({
backgroundColor: "blue",
padding: 10,
margin: 5
});
expect(styledText).toHaveStyle({
fontSize: 16,
color: "red",
fontWeight: "bold"
});
// Negative assertions
expect(styledView).not.toHaveStyle({ backgroundColor: "red" });
expect(styledView).not.toHaveStyle({ padding: 20 });
expect(styledText).not.toHaveStyle({ fontSize: 20 });
});Matchers for testing element containment and hierarchy.
/**
* Assert element contains another element
* @param element - Element that should be contained
*/
expect(container).toContainElement(element: ReactTestInstance): void;
expect(container).not.toContainElement(element: ReactTestInstance): void;Usage Examples:
test("containment matchers", () => {
render(
<View testID="outer-container">
<View testID="inner-container">
<Text testID="nested-text">Nested content</Text>
<Pressable testID="nested-button">
<Text>Button</Text>
</Pressable>
</View>
<Text testID="sibling-text">Sibling content</Text>
</View>
);
const outerContainer = screen.getByTestId("outer-container");
const innerContainer = screen.getByTestId("inner-container");
const nestedText = screen.getByTestId("nested-text");
const nestedButton = screen.getByTestId("nested-button");
const siblingText = screen.getByTestId("sibling-text");
// Direct containment
expect(outerContainer).toContainElement(innerContainer);
expect(outerContainer).toContainElement(siblingText);
expect(innerContainer).toContainElement(nestedText);
expect(innerContainer).toContainElement(nestedButton);
// Nested containment (transitive)
expect(outerContainer).toContainElement(nestedText);
expect(outerContainer).toContainElement(nestedButton);
// Negative containment
expect(innerContainer).not.toContainElement(siblingText);
expect(nestedText).not.toContainElement(nestedButton);
expect(siblingText).not.toContainElement(nestedText);
});
test("dynamic containment", () => {
const DynamicContainer = ({ showNested }) => (
<View testID="dynamic-container">
<Text testID="always-present">Always here</Text>
{showNested && (
<View testID="conditional-nested">
<Text testID="nested-content">Conditional content</Text>
</View>
)}
</View>
);
const { rerender } = render(<DynamicContainer showNested={false} />);
const container = screen.getByTestId("dynamic-container");
const alwaysPresent = screen.getByTestId("always-present");
// Always present element
expect(container).toContainElement(alwaysPresent);
// Conditional element not present initially
expect(screen.queryByTestId("conditional-nested")).not.toBeOnTheScreen();
// Show nested content
rerender(<DynamicContainer showNested={true} />);
const conditionalNested = screen.getByTestId("conditional-nested");
const nestedContent = screen.getByTestId("nested-content");
// Conditional elements now present and contained
expect(container).toContainElement(conditionalNested);
expect(container).toContainElement(nestedContent);
expect(conditionalNested).toContainElement(nestedContent);
});While the library provides comprehensive built-in matchers, you can also extend functionality if needed.
/**
* Jest matcher result type for custom matchers
*/
interface MatcherResult {
pass: boolean;
message: () => string;
}
/**
* Custom matcher function signature
*/
type CustomMatcher<T = ReactTestInstance> = (
received: T,
...args: any[]
) => MatcherResult;Usage Examples:
// Custom matcher example (typically in test setup file)
expect.extend({
toHaveCustomProperty(received, expectedValue) {
const pass = received.props.customProperty === expectedValue;
return {
pass,
message: () =>
pass
? `Expected element not to have customProperty: ${expectedValue}`
: `Expected element to have customProperty: ${expectedValue}, but got: ${received.props.customProperty}`
};
}
});
// Usage in tests
test("custom matcher", () => {
render(
<View testID="custom-element" customProperty="special-value">
<Text>Custom Element</Text>
</View>
);
const element = screen.getByTestId("custom-element");
expect(element).toHaveCustomProperty("special-value");
});Install with Tessl CLI
npx tessl i tessl/npm-testing-library--react-native