Testing React Native With the New Jest - Part I

Authors
Ferran Negre
Software Engineer
@
Callstack

In my journey with React, I have been always testing and encouraging people who surround me to test their apps. I have always used Jest for it. But let’s not talk about it again and let’s focus on React Native. Wouldn’t it be nice to use the same framework to test our amazing mobile apps? Furthermore… Wouldn’t it be nice to reuse some tests if we are reusing logic between our web app and our mobile app?

I will try to answer those questions in the following series. In this first article, I will focus on testing React Native components and the pros and cons of using the new Snapshot system provided by Jest vs. shallow rendering comparison (test utils and Enzyme).

Why the new Jest?

Before you ask me why I am calling it the new Jest, please check out the updates notes from Jest 14 and Jest 15. It is not only me who says that Jest has a total new face compared to 1 year ago… It is also his number one core contributor:

yeah, it has nothing to do with what Jest was then. Just the same name and logo.

— Christoph Nakazawa (@cpojer) September 2, 2016

In this series we will focus on one of the new Jest features: Snapshot testing.

Jest can capture snapshots of React trees or other serializable values to write tests quickly and it provides a seamless update experience.

Let’s begin to prove the above quote!

Set up Jest in a React Native project

Trivial, seriously. Just follow the docs:

// Install dependencies
npm install --save-dev jest babel-jest jest-react-native babel-preset-react-native react-test-renderer
// package.json
"scripts": {
"test": "jest"
},
"jest": {
"preset": "jest-react-native"
}
// .babelrc
{
"presets": ["react-native"]
}

DONE. Try it out with:

npm test
view raw gistfile1.txt hosted with ❤ by GitHub

Time to test components

The example repo for this article renders a list of Github repositories. You can read several test examples in there but for the purpose this article, let’s focus on testing a simple component called RepoItem:

const RepoItem = (props) => {
const { description, id, name, stargazers_count } = props.repo;
const itemStyle = props.isSelected && [styles.item, styles.selected] || styles.item;
return (
<TouchableHighlight
onPress={() => {props.selectRepo(id)}}
underlayColor='#E0F2F1'
>
<View style={itemStyle}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.stars}>{`${stargazers_count} stars`}</Text>
</View>
</TouchableHighlight>
);
};
view raw RepoItem.1.js hosted with ❤ by GitHub

A full file includes the props definition and styles.

This component has an interesting prop called isSelected. When isSelected = true, then the item renders with a green background.

The problems of Shallow Rendering comparison

Have you ever used Shallow rendering from react-addons-test-utils or Enzyme? They are pretty similar, Enzyme got popular cause it has a nicer API which also allows you to find elements in your React trees. Let’s choose this one and try to test a React Native component using it.

Enzyme

First problems when trying to use the shallow function from Enzyme (forget about mount in react-native):

react-addons-test-utils is an implicit dependency in order to support react@0.13–14. Please add the appropriate version to your devDependencies. See https://github.com/airbnb/enzyme#installation

react-dom is an implicit dependency in order to support react@0.13–14. Please add the appropriate version to your devDependencies. See https://github.com/airbnb/enzyme#installation

npm install react-dom react-addons-test-util --save-dev

So we actually need to install react-dom and react-addons-test-util to our devDependencies for Enzyme to work… Ouch!:

Ok, let’s go and try to do the first full shallow comparison:

const wrapper = shallow(
<RepoItem
repo={repos}
selectRepo={jest.fn}
/>
);
const { description, id, name, stargazers_count } = repo;
expect(wrapper.contains(
<TouchableHighlight
onPress={() => {}}
underlayColor='#E0F2F1'
>
<View style={styles.item}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.stars}>{`${stargazers_count} stars`}</Text>
</View>
</TouchableHighlight>
)).toBe(true);

The onPress function will make this test to fail.

Ok, actually, this first example won’t work. See the onPress property? The first problem that we have here is the way it’s defined in the component:

onPress={() => {props.selectRepo(id)}}
view raw onpress.js hosted with ❤ by GitHub

Both functions won’t be the same (we are creating a new function instance) thus the comparison will fail. We could make some workarounds here but… Let’s be lazy and skip the TouchableHighlightpart and assume that nobody will break it:

const wrapper = shallow(
<RepoItem repo={repos} />
);
const { description, name, stargazers_count } = repo;
expect(wrapper.contains(
<View style={styles.item}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.stars}>{`${stargazers_count} stars`}</Text>
</View>
)).toBe(true);

This will work but…

Don’t you feel we are reimplementing the render() again? Any prop you add/remove or any change you make… You need to come back here and do the same.

All right, now we want to test the component when the prop isSelected is set to true:

it('renders a selected RepoItem using Enzyme 1', () => {
const wrapper = shallow(
<RepoItem
repo={repo}
selectRepo={jest.fn}
isSelected
/>
);
const { description, name, stargazers_count } = repo;
expect(wrapper.contains(
<View style={[styles.item, styles.selected]}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.stars}>{`${stargazers_count} stars`}</Text>
</View>
)).toBe(true);
});

Copy-paste-copy-paste…

Since recreating the bigger picture gets so repetitive, developers tend to test only the dynamic parts, see:

it('renders a RepoItem using Enzyme 2', () => {
const wrapper = shallow(
<RepoItem
repo={repo}
selectRepo={jest.fn}
/>
);
expect(wrapper.find({style: styles.item}).length).toBe(1);
});
it('renders a selected RepoItem using Enzyme 2', () => {
const wrapper = shallow(
<RepoItem
isSelected
repo={repo}
selectRepo={jest.fn}
/>
);
expect(wrapper.find({style: [styles.item, styles.selected]}).length).toBe(1);
});

Yes, you can create a function that gives you the shallow component and accepts the isSelected prop

This seems better, right? Let’s continue…

Assume that at some point on this project, our client decides that when you click a list item, a part from the selected background color, we also want to show more information, specifically, the repo description. When isSelected = true, the item renders with a green background and shows the repo description

With this change, two things will happen:

  1. The test that checks all the render output will fail: We will need to go back and reimplement it
  2. The test that checks only the dynamic part will pass anyway (Oops! is that good?)

In the first case, we need to go back and copy-paste this line into it’s corresponded place:

expect(wrapper.contains(
<View style={[styles.item, styles.selected]}>
<Text style={styles.title}>{name}</Text>
<Text style={styles.stars}>{`${stargazers_count} stars`}</Text>
<Text>{description}</Text>
</View>
)).toBe(true);

When isSelected prop is true

In the second case, we need to remember to go back to the test and add the new cases:

// isSelected = false
expect(wrapper.contains(<Text>{repo.description}</Text>)).toBe(false);
// isSelected = true
expect(wrapper.contains(<Text>{repo.description}</Text>)).toBe(true);

New cases

Do you see the problems? The way I see it… The bigger picture (full render) is complicated to create, tedious to maintain and very repetitive (copy-paste). The second approach (using find, contains…) is unstable: We need to remember to implement all the cases since the new ones won’t break those tests.

Check the full RepoItem-Shallow-test.js file for a bigger picture.

Shallow rendering summary

Pros

  • Powerful selector api (find, contains, etc)
  • It is actually working in React Native (shallow part)

Cons

  • It feels you are repeating yourself all the time, thus, people will usually tend to test only specific parts of the output (render) instead of the big picture
  • Copy-paste from the component to the test
  • Difficult to maintain
  • Go try convincing people to write them

Snapshots to the rescue

Jest 14 introduced Snapshots testing: Check out why they are so great:

No flakiness: Because tests are run in a command line runner instead of a real browser or on a real phone, the test runner doesn’t have to wait for builds, spawn browsers, load a page and drive the UI to get a component into the expected state which tends to be flaky and the test results become noisy.

Fast iteration speed: Engineers want to get results in less than a second rather than waiting for minutes or even hours. If tests don’t run quickly like in most end-to-end frameworks, engineers don’t run them at all or don’t bother writing them in the first place.

Debugging: It’s easy to step into the code of an integration test in JS instead of trying to recreate the screenshot test scenario and debugging what happened in the visual diff.

After the read, let’s try it out and convince ourselves.

We can write a Snapshot test for our first RepoItem (when isSelected is set to false):

it('renders a RepoItem using Snapshots', () => {
expect(renderer.create(
<RepoItem
repo={repos[0]}
selectRepo={jest.fn}
/>
)).toMatchSnapshot();
});

Check the result at: RepoItem-Snapshot-test.snap. You will find that it does not use the background color property (as expected) because isSelected is false:

// RepoItem-Snapshot-test.snap
...
style={
Array [
Object {
"backgroundColor": "transparent"
},
undefined
]
}
...

Now, let’s test the output when isSelected is set to true (it needs to render a green background). Since our component is actually a pure function, this becomes trivial:

it('renders a selected RepoItem using Snapshots', () => {
expect(renderer.create(
<RepoItem
isSelected
repo={repos[0]}
selectRepo={jest.fn}
/>
)).toMatchSnapshot();
});

And we can find the expected part inside the resulted snapshot RepoItem-Snapshot-test.snap:

// RepoItem-Snapshot-test.snap
style={
Array [
Object {
"paddingHorizontal": 24,
"paddingVertical": 8
},
Object {
"backgroundColor": "#B2DFDB"
}
]
}>

Great! Now, remember that our client came and asked us to implement the expanded part to show the repo description (a part from the background color). Let’s run the tests.

Oops! A test has failed.

Let’s check why. At developing time, Jest will show us very useful error outputs (you can use the watch argument to see them live). Since this change is expected, we can run:

npm test -- -u RepoItem-Snapshot
view raw npm test.js hosted with ❤ by GitHub

And that will update our snapshot for free. Check it out:

...
50000 stars
</Text>
<Text
accessible={true}
allowFontScaling={true}
ellipsizeMode="tail">
Useful description goes here.
</Text>
</View>
</View>
...
view raw 5000 test.js hosted with ❤ by GitHub

Boom! Our test failed, we inspected it, the failure was expected and we re-generated the snapshot. We did not have to modify our test. Moreover, this snapshot difference will be shown in our next Pull Request.

And that is all, seriously. Our render output is fully tested for both cases and it is capable to react to changes giving us nice error outputs and an easy way to fix them. Quiet neat, right?

Can I test components with internal state?

Before you ask, yes, you can. Check this example in the Jest repo: Link.react-test.js. Of course, if your component has internal state (side effects), it will get much more complicated to test. See:

I am missing a selector api like Enzyme

Especially if your component has state and you need to trigger some event, it can be very tedious to find the node inside the tree. However, this question has been already asked at Jest issues and it has an official answer:

We are working on a selector API for the test renderer which will make this possible through an official API — Christoph Pojer

Snapshots summary

Pros

  • Easy to write
  • Don’t repeat yourself (DRY)
  • Compatible with React Native: Check RepoList-Snapshot-test.js and its snapshot which renders a ListView with its items (try to do that without snapshots if you can)
  • Error messages on develop time (you can use —watch t00)
  • More goodies to come!

Cons

  • Missing a selector API like Enzyme (but you read above that Jest community is working on it)
  • Snapshots can be huge and sometimes it might be tedious to find the part you are looking for (lot of noise). In contrast, pull requests will highlight any changes for you. Update 27 Jul 2017: We created snapshot-diff to overcome this issue. Third part of this article is coming soon.
  • I am missing a comparison-tool-like between Snapshots. In my example, I manually need to look the differences between the Snapshot with isSelected set to false vs. isSelected set to true

What is the true value of Snapshots?

I want to wrap up stressing the pull request part again. From Kent C. Dodds:

.@spencercarnage that's not where the value is... The value is in this [1] at develop time and this [2] at PR time pic.twitter.com/ULlRaD4EIZ

— Kent C. Dodds 💿🔴 (@kentcdodds) September 4, 2016

I could not agree more: Code reviews shine using snapshots. But, be aware then that for the Snapshot concept to work you need to:

but I need to stress that when using snapshots it's integral that the resulting file is being reviewed properly.

— Christoph Nakazawa (@cpojer) September 5, 2016

Which means:

You need to make sure that the __snapshots__ folders are reviewed in your PRs

Let me add yet another value: Don’t repeat yourself. This has been demonstrated (I hope!) here when we do not have to re-rewrite our component output. But if you want to go further on that, check out the next article of this series.

And if your team could use help from senior developers, check out our React Native development services. We’d love to talk and find out how we can take your React Native project to the next level together.

Insights

Learn more about Testing

How AI Agents Can Cut Manual QA by 30% — cover

Oct 30Article

How AI Agents Can Cut Manual QA by 30%

Explore an agentic QA workflow targeting 30% less manual effort across mobile, web, and TV, with test selection, failure triage, and validation.

Give QA Agents the Controls They Need With Appduct — cover

Oct 20Article

Give QA Agents the Controls They Need With Appduct

Use Appduct to give AI QA agents access to app functions for setting test conditions, triggering events, and checking results with UI automation.

cover

Oct 13Article

Agentic Mobile Testing With e2e and agent-device

See how e2e uses agent-device for AI-driven iOS and Android tests, combines exact assertions with agent actions, and replays cached steps without model calls.

Who Tests AI-Written Code? Inside TesterArmy With Szymon Rybczak — cover

Oct 12Podcast

Who Tests AI-Written Code? Inside TesterArmy With Szymon Rybczak

Szymon Rybczak explains how TesterArmy tests web and mobile apps with AI agents, creates test plans for pull requests, and fits into QA workflows.

cover

Aug 4Article

Automating Mobile QA With Cloud Agents

Code review and green CI checks do not prove a mobile app still works. Learn how cloud agents, agent-device, and runtime evidence can verify app behavior before merge.

cover

Jul 21Webinar

Behavior Review for AI-Native Delivery

AI can increase code output faster than teams can verify the real app. See why behavior review matters before merge.

cover

Jul 6Article

Introducing Sniffler: Run only the E2E tests your changes can affect

Sniffler maps changed files to affected E2E tests, so teams can avoid running the full suite on every pull request.

cover

Jun 17Talk

Automating Mobile QA with Cloud Agents

Michał Pierzchała will present Automating Mobile QA with Cloud Agents at Ship London on 17 June 2026.

cover

May 13Podcast

How AI Is Changing React Native Development and Testing

Zack Ebenfeld joins React Universe On Air to talk about how AI is changing React Native workflows, testing, QA, and day-to-day engineering work.

cover

Apr 14Article

Agent React DevTools: Give AI Agents Access to React Internals

Agent React DevTools gives AI agents access to the React component tree, state, and performance data, enabling deeper debugging and more reliable QA for React and React Native apps.

cover

Mar 19Live Stream

Agent Device, AI workflows, and mobile testing in practice

A discussion on Agent Device, AI-driven workflows, and testing mobile apps using simulators and accessibility data.

cover

Aug 23Podcast

Introducing E2E Tests to React Native Core

This episode of the React Native Show dives into the recent introduction of End-to-End (E2E) testing in RNTester. Join us as we unpack the thought process, technical considerations, and practical implementation of this new approach to testing in React Native.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.