Testing

Back

Loading concept...

🧪 React Native Testing: Your App’s Safety Net

The Story of the Careful Builder

Imagine you’re building a LEGO spaceship. You wouldn’t just throw pieces together and hope it flies, right? You’d test each part:

  • Does the wing click on properly?
  • Do the wheels spin?
  • Does the door open and close?

Testing your React Native app is exactly the same! You check each piece before launching into space (the App Store). Let’s learn how to be careful builders! 🚀


🎯 What We’ll Learn

graph TD A["Testing in React Native"] --> B["Jest Setup"] A --> C["Unit Testing"] A --> D["Mocking"] A --> E["Snapshot Testing"] A --> F["Testing Async Code"] A --> G["React Native Testing Library"] A --> H["E2E Testing Concepts"]

1️⃣ Jest Setup: Getting Your Test Kitchen Ready

The Analogy

Think of Jest as your test kitchen. Before you can bake cookies (run tests), you need:

  • An oven (Jest)
  • Measuring cups (configuration)
  • Ingredients (your code)

What is Jest?

Jest is a testing tool made by Facebook. It comes FREE with React Native! It’s like having a robot helper that checks your work automatically.

Setting Up Jest

Good news! When you create a React Native app, Jest is already there. Let’s peek at the setup:

package.json:

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "preset": "react-native"
  }
}

What does this mean?

  • "test": "jest" → When you type npm test, it runs Jest
  • "preset": "react-native" → Jest knows it’s testing a React Native app

Your First Test File

Create a file ending in .test.js:

// math.test.js
test('adding 1 + 1 equals 2', () => {
  expect(1 + 1).toBe(2);
});

Breaking it down:

  • test() → Says “Hey Jest, here’s a test!”
  • First part → Name of your test (what are we checking?)
  • expect() → The actual check
  • .toBe() → Should equal this value

Running Tests

npm test

You’ll see:

✓ adding 1 + 1 equals 2

Green checkmark = SUCCESS! 🎉


2️⃣ Unit Testing: Testing One Piece at a Time

The Analogy

Imagine testing a flashlight. Unit testing means you check:

  • Does the button work? (just the button)
  • Does the bulb light up? (just the bulb)
  • Do batteries fit? (just the battery slot)

You test each UNIT separately!

What is a Unit?

A unit is the smallest testable piece of your code:

  • A function
  • A component
  • A calculation

Example: Testing a Function

// helpers.js
export function greet(name) {
  return `Hello, ${name}!`;
}
// helpers.test.js
import { greet } from './helpers';

test('greet says hello to a name', () => {
  const result = greet('Luna');
  expect(result).toBe('Hello, Luna!');
});

test('greet works with any name', () => {
  expect(greet('Max')).toBe('Hello, Max!');
  expect(greet('Zoe')).toBe('Hello, Zoe!');
});

Testing a Simple Component

// Button.js
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';

export default function Button({ label }) {
  return (
    <TouchableOpacity>
      <Text>{label}</Text>
    </TouchableOpacity>
  );
}
// Button.test.js
import React from 'react';
import { render } from '@testing-library/react-native';
import Button from './Button';

test('Button shows correct label', () => {
  const { getByText } = render(
    <Button label="Click Me" />
  );
  expect(getByText('Click Me')).toBeTruthy();
});

Why Unit Testing?

Without Testing With Testing
😰 “I hope this works” 😊 “I KNOW this works”
🐛 Bugs hide everywhere 🔍 Bugs found early
😱 Scared to change code 💪 Confident changes

3️⃣ Mocking: Pretend Helpers

The Analogy

Imagine you’re practicing a play, but your friend who plays the wizard is sick. You ask someone else to pretend to be the wizard—they say the wizard’s lines, stand in the wizard’s spot.

That’s mocking! Creating pretend versions of things.

Why Mock?

Sometimes your code needs:

  • The internet (API calls)
  • A database
  • The phone’s camera

But in tests, we don’t want real internet or cameras. We use mocks—pretend versions!

Mocking a Function

// api.js
export async function fetchUser(id) {
  // This would normally call the internet
  const response = await fetch(`/users/${id}`);
  return response.json();
}
// api.test.js
import { fetchUser } from './api';

// Tell Jest to use a pretend version
jest.mock('./api');

test('fetchUser returns user data', async () => {
  // Set up the pretend response
  fetchUser.mockResolvedValue({
    id: 1,
    name: 'Luna'
  });

  const user = await fetchUser(1);

  expect(user.name).toBe('Luna');
});

Mocking React Native Modules

Some phone features need mocking:

// jest.setup.js
jest.mock('react-native', () => ({
  Alert: {
    alert: jest.fn()
  },
  Platform: {
    OS: 'ios'
  }
}));

Common Mock Patterns

graph TD A["What to Mock"] --> B["API Calls"] A --> C["Native Modules"] A --> D["Navigation"] A --> E["Storage"] B --> B1["fetch, axios"] C --> C1["Camera, GPS"] D --> D1["useNavigation"] E --> E1["AsyncStorage"]

Mock Example: AsyncStorage

// Mock AsyncStorage
jest.mock('@react-native-async-storage/async-storage',
  () => ({
    setItem: jest.fn(),
    getItem: jest.fn(() => Promise.resolve('stored-value')),
    removeItem: jest.fn(),
  })
);

4️⃣ Snapshot Testing: Taking Photos of Your UI

The Analogy

Imagine taking a photo of your room every day. If something changes (your lamp moved), you’d notice by comparing photos!

Snapshot testing takes a “photo” of your component’s code output. If it changes unexpectedly, Jest tells you!

How It Works

// Card.js
import React from 'react';
import { View, Text } from 'react-native';

export default function Card({ title }) {
  return (
    <View>
      <Text style={{ fontWeight: 'bold' }}>
        {title}
      </Text>
    </View>
  );
}
// Card.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Card from './Card';

test('Card matches snapshot', () => {
  const tree = renderer
    .create(<Card title="Hello" />)
    .toJSON();

  expect(tree).toMatchSnapshot();
});

What Happens?

First run: Jest creates a file called Card.test.js.snap:

exports[`Card matches snapshot 1`] = `
<View>
  <Text
    style={
      Object {
        "fontWeight": "bold",
      }
    }
  >
    Hello
  </Text>
</View>
`;

Future runs: Jest compares current output to this saved snapshot.

When Snapshots Fail

If the component changes:

Snapshot failed
- Expected
+ Received

  <View>
    <Text
      style={
        Object {
-         "fontWeight": "bold",
+         "fontWeight": "normal",
        }
      }
    >

Was the change intentional?

  • Yes: Run npm test -- -u to update snapshot
  • No: You found a bug! Fix it!

Snapshot Best Practices

Do ✅ Don’t ❌
Use for UI components Use for logic/data
Keep snapshots small Snapshot huge trees
Review snapshot changes Auto-update blindly

5️⃣ Testing Async Code: Waiting for Things

The Analogy

Imagine ordering pizza. You can’t eat it the moment you order—you have to wait! Testing async code means telling Jest to wait for things to finish.

The Problem

This test would FAIL:

// ❌ WRONG - doesn't wait!
test('fetches data', () => {
  let data;
  fetchData().then(result => {
    data = result;
  });
  expect(data).toBe('hello'); // data is still undefined!
});

Solution 1: Async/Await

// ✅ CORRECT - waits properly!
test('fetches data', async () => {
  const data = await fetchData();
  expect(data).toBe('hello');
});

The magic words: async and await!

Solution 2: Return a Promise

test('fetches data', () => {
  return fetchData().then(data => {
    expect(data).toBe('hello');
  });
});

Solution 3: Done Callback

test('fetches data', done => {
  fetchData().then(data => {
    expect(data).toBe('hello');
    done(); // Tell Jest we're finished
  });
});

Testing with Timers

Sometimes code uses setTimeout:

// utils.js
export function delayedHello(callback) {
  setTimeout(() => {
    callback('Hello!');
  }, 1000);
}
// utils.test.js
jest.useFakeTimers();

test('calls callback after delay', () => {
  const callback = jest.fn();

  delayedHello(callback);

  // Time hasn't passed yet
  expect(callback).not.toHaveBeenCalled();

  // Fast-forward time!
  jest.runAllTimers();

  expect(callback).toHaveBeenCalledWith('Hello!');
});

Async Flow

graph TD A["Start Test"] --> B{Is code async?} B -->|No| C["Run normally"] B -->|Yes| D["Use async/await"] D --> E["Jest waits..."] E --> F["Promise resolves"] F --> G["Check result"] C --> G G --> H["Test complete!"]

6️⃣ React Native Testing Library: Testing Like Users

The Analogy

Imagine testing a toy car. You could:

  • Option A: Look inside at the gears and wires
  • Option B: Play with it like a kid would

React Native Testing Library chooses Option B! Test what users see and do.

Why This Approach?

“The more your tests resemble the way your software is used, the more confidence they give you.”

Instead of testing internal code, test:

  • What text appears?
  • What happens when I tap?
  • Can I find this button?

Setting Up

npm install --save-dev @testing-library/react-native

Core Methods

Finding Elements

import { render } from '@testing-library/react-native';

const { getByText, getByTestId, queryByText } =
  render(<MyComponent />);
Method When to Use
getByText Find by visible text
getByTestId Find by testID prop
getByRole Find by accessibility role
queryByText Check if element exists (returns null if not)

Testing User Actions

import { render, fireEvent } from
  '@testing-library/react-native';

test('button press updates count', () => {
  const { getByText } = render(<Counter />);

  const button = getByText('Add');

  fireEvent.press(button);

  expect(getByText('Count: 1')).toBeTruthy();
});

Full Example

// LoginForm.js
import React, { useState } from 'react';
import { View, TextInput, Button, Text } from 'react-native';

export default function LoginForm({ onSubmit }) {
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  const handleSubmit = () => {
    if (!email.includes('@')) {
      setError('Invalid email');
      return;
    }
    onSubmit(email);
  };

  return (
    <View>
      <TextInput
        testID="email-input"
        value={email}
        onChangeText={setEmail}
        placeholder="Enter email"
      />
      {error ? <Text>{error}</Text> : null}
      <Button title="Submit" onPress={handleSubmit} />
    </View>
  );
}
// LoginForm.test.js
import React from 'react';
import { render, fireEvent } from
  '@testing-library/react-native';
import LoginForm from './LoginForm';

test('shows error for invalid email', () => {
  const { getByTestId, getByText } =
    render(<LoginForm onSubmit={() => {}} />);

  const input = getByTestId('email-input');
  fireEvent.changeText(input, 'notanemail');

  const submitBtn = getByText('Submit');
  fireEvent.press(submitBtn);

  expect(getByText('Invalid email')).toBeTruthy();
});

test('calls onSubmit with valid email', () => {
  const mockSubmit = jest.fn();
  const { getByTestId, getByText } =
    render(<LoginForm onSubmit={mockSubmit} />);

  fireEvent.changeText(
    getByTestId('email-input'),
    'test@example.com'
  );
  fireEvent.press(getByText('Submit'));

  expect(mockSubmit).toHaveBeenCalledWith('test@example.com');
});

7️⃣ E2E Testing Concepts: The Full Journey

The Analogy

Unit tests check individual LEGO pieces. E2E (End-to-End) tests build the entire spaceship and fly it!

E2E tests act like a real user—tapping buttons, scrolling, typing—on a real (or simulated) device.

Unit vs E2E

graph LR subgraph Unit Tests A["Test function"] --> B["Fast"] A --> C["Isolated"] end subgraph E2E Tests D["Test whole app"] --> E["Slower"] D --> F["Real device"] end

Popular E2E Tools

Tool Made By Best For
Detox Wix React Native
Appium Open Source Cross-platform
Maestro mobile.dev Simple flows

Detox Example

// e2e/login.test.js
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  it('should login successfully', async () => {
    // Type in email
    await element(by.id('email-input'))
      .typeText('user@test.com');

    // Type password
    await element(by.id('password-input'))
      .typeText('secret123');

    // Tap login button
    await element(by.text('Login'))
      .tap();

    // Verify we see home screen
    await expect(element(by.text('Welcome')))
      .toBeVisible();
  });
});

E2E Testing Pyramid

graph TD A["Testing Pyramid"] --> B["🔺 E2E Tests&lt;br/&gt;Few, slow, high confidence"] A --> C["🔶 Integration Tests&lt;br/&gt;Medium amount"] A --> D["🟩 Unit Tests&lt;br/&gt;Many, fast, focused"]

The idea: Have MANY unit tests, SOME integration tests, and FEW E2E tests.

When to Use E2E

Good for:

  • Critical user flows (login, checkout)
  • Smoke tests before release
  • Catching integration bugs

Not good for:

  • Testing every edge case
  • Fast feedback during development
  • Testing internal logic

E2E Best Practices

  1. Test critical paths only - Login, main features
  2. Keep tests independent - Each test starts fresh
  3. Use stable selectors - testID over text
  4. Handle async properly - Wait for elements
  5. Run on CI - Automated on every push

🎯 Summary: Your Testing Toolkit

graph TD A["Your App"] --> B["Unit Tests&lt;br/&gt;Individual pieces"] A --> C[Snapshot Tests<br/>UI doesn't change] A --> D["Integration Tests&lt;br/&gt;Pieces work together"] A --> E["E2E Tests&lt;br/&gt;Full user journey"] B --> F["Jest + RNTL"] C --> F D --> F E --> G["Detox/Appium"]

Quick Reference

Type Speed Confidence When to Use
Unit ⚡ Fast 🎯 Focused Every function
Snapshot ⚡ Fast 📸 UI structure Components
Async 🏃 Medium ⏳ APIs/timers Network calls
E2E 🐢 Slow 🔝 Highest Critical flows

You Did It! 🎉

You now understand:

  • ✅ How to set up Jest
  • ✅ Writing unit tests
  • ✅ Creating mocks for external code
  • ✅ Using snapshots for UI
  • ✅ Testing async operations
  • ✅ React Native Testing Library
  • ✅ E2E testing concepts

Remember: Tests are your safety net! They catch bugs before users do, and give you confidence to improve your app without fear.

Happy testing! 🧪✨

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.