Troubleshooting Platform-Specific Issues and Improving Test Coverage in React Native
Course: React Native Training
Topic: Troubleshooting Platform-Specific Issues and Improving Test Coverage
Course Link: React Native Training at JustAcademy
Demo Link: Register for a Course Demo
JustAcademy Website: Visit JustAcademy
1. Introduction
React Native allows developers to build mobile applications for Android and iOS using a shared JavaScript or TypeScript codebase. Although many components and business logic can be reused across platforms, Android and iOS differ in their operating systems, native APIs, permissions, UI behavior, navigation conventions, and build configurations.
These differences can cause an application to behave correctly on one platform but fail on another. For example, a permission dialog may behave differently on Android and iOS, a native dependency may work on an iOS simulator but fail on an Android device, or a component may render differently because of platform-specific layout behavior.
Automated testing helps identify these problems early. Jest and React Native Testing Library can verify JavaScript logic and component behavior, while platform-specific integration tests and physical-device testing help identify issues involving native functionality.
1.1 Learning Objectives
- Understand common Android and iOS differences in React Native applications.
- Identify platform-specific errors and their possible causes.
- Use platform-specific code safely and maintainably.
- Troubleshoot dependency, build, permission, layout, and navigation issues.
- Write tests for platform-specific behavior.
- Understand Jest coverage reports and their limitations.
- Improve statement, branch, function, and line coverage.
- Develop a repeatable debugging and testing workflow.
2. Understanding Platform-Specific Issues
A platform-specific issue is a bug or unexpected behavior that occurs on one operating system, device type, or runtime environment but does not necessarily occur in another.
2.1 Common Examples
| Issue |
Possible Android Behavior |
Possible iOS Behavior |
| Permissions |
Runtime permissions may require manifest declarations and permission requests. |
Privacy descriptions and authorization flows may be required in the application configuration. |
| Navigation |
Hardware or system Back behavior may affect screen transitions. |
Swipe-back gestures and navigation conventions may affect transitions. |
| Layout |
Status bars, navigation bars, and screen sizes affect available space. |
Safe areas, notches, and home indicators affect available space. |
| Keyboard |
Keyboard resizing and window adjustment settings may affect layouts. |
Keyboard appearance and safe-area behavior may differ. |
| Fonts |
Font rendering may vary with Android versions and installed fonts. |
System fonts and text metrics may differ. |
| Native dependencies |
A package may require Android-specific Gradle configuration. |
A package may require CocoaPods or Xcode configuration. |
| File access |
Storage access depends on Android version and permission rules. |
File access follows iOS sandbox and document-picker rules. |
| Animations |
Performance can vary with device hardware and native configuration. |
Performance can vary with device hardware and native configuration. |
2.2 Why Platform-Specific Issues Occur
- Different operating-system APIs and permission models.
- Differences in native module implementations.
- Incompatible dependency or toolchain versions.
- Differences in screen dimensions, pixel density, and safe areas.
- Different JavaScript engines, runtime behavior, or native rendering details.
- Differences between simulators, emulators, and physical devices.
- Platform-specific configuration files and build settings.
- Code that assumes a feature is available on every platform.
3. Using Platform.OS to Identify the Current Platform
React Native provides the Platform module to identify the operating system on which the application is running.
3.1 Basic Platform Detection
import { Platform } from 'react-native';
if (Platform.OS === 'android') {
console.log('Running on Android');
} else if (Platform.OS === 'ios') {
console.log('Running on iOS');
} else {
console.log('Running on another platform');
}
The Platform.OS property commonly returns values such as android, ios, web, or another supported platform identifier, depending on the project environment.
3.2 Platform-Specific Values
The Platform.select() method chooses a value based on the current platform.
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
padding: Platform.select({
ios: 20,
android: 16,
default: 12
}),
backgroundColor: Platform.select({
ios: '#F2F2F7',
android: '#FFFFFF',
default: '#FFFFFF'
})
}
});
This approach is useful when small style or configuration differences are required. Avoid adding platform conditions when the same implementation works reliably on both platforms.
3.3 Best Practices for Platform Detection
- Keep platform-specific conditions small and easy to understand.
- Use shared components wherever possible.
- Isolate complex platform differences in separate modules.
- Provide a sensible default for any additional supported platform.
- Test both branches when behavior differs between platforms.
4. Using Platform-Specific Files
When implementations differ significantly between Android and iOS, React Native supports platform-specific file extensions. This allows the application to select the correct implementation without placing extensive conditional logic inside a single file.
4.1 Example File Structure
src/
components/
PlatformMessage.js
PlatformMessage.android.js
PlatformMessage.ios.js
In projects using React Native's platform-specific module resolution, imports can often omit the platform suffix.
import PlatformMessage from './components/PlatformMessage';
The bundler selects the matching platform-specific implementation. Confirm the project's resolver and build configuration if you use additional platforms or custom tooling.
4.2 Android Implementation
Create PlatformMessage.android.js.
import React from 'react';
import { Text } from 'react-native';
export default function PlatformMessage() {
return (
<Text>Android-specific message</Text>
);
}
4.3 iOS Implementation
Create PlatformMessage.ios.js.
import React from 'react';
import { Text } from 'react-native';
export default function PlatformMessage() {
return (
<Text>iOS-specific message</Text>
);
}
4.4 When to Use Platform-Specific Files
- When a native API has significantly different implementations.
- When a complex component requires different platform behavior.
- When native integration differs between Android and iOS.
- When keeping platform-specific logic separate improves maintainability.
Do not create separate files for every minor difference. A single shared implementation with a small, clear platform condition is often easier to maintain.
5. Troubleshooting Common Android Issues
Android applications run on devices with different screen sizes, Android versions, hardware configurations, and manufacturer customizations. Troubleshooting should begin by identifying the affected device, operating-system version, application build, and steps needed to reproduce the issue.
5.1 Android Build Failures
Android build errors may occur because of incompatible Gradle versions, Android Gradle Plugin versions, Java versions, SDK configuration, native dependencies, or incorrect manifest settings.
Useful commands include:
npx react-native doctor
cd android
./gradlew assembleDebug
On Windows, use the Gradle wrapper batch file:
cd android
gradlew.bat assembleDebug
The React Native diagnostic command can identify common environment configuration problems. The Gradle commands help reproduce Android build failures independently of the JavaScript bundler.
5.2 Common Android Build Problems
| Problem |
Possible Cause |
Troubleshooting |
| Gradle build failure |
Incompatible build tool or dependency versions. |
Review the complete Gradle error and verify compatible versions. |
| SDK not found |
Incorrect Android SDK path or missing SDK components. |
Check Android Studio SDK settings and local environment configuration. |
| Java version error |
The selected JDK does not match project requirements. |
Use the JDK version supported by the project's React Native and Gradle versions. |
| Manifest merge conflict |
Conflicting attributes declared by the application and dependencies. |
Inspect the manifest merger report and resolve conflicting declarations. |
| Native module compilation error |
A package needs additional Android configuration. |
Follow the package's installation and compatibility instructions. |
| App installs but crashes |
Runtime exception, native initialization error, or missing configuration. |
Inspect Android Logcat and reproduce the crash. |
5.3 Clearing Android Build Artifacts
Stale build artifacts can occasionally cause confusing build failures. Cleaning the project can help, but it should not replace investigating the original error.
cd android
./gradlew clean
cd ..
npx react-native run-android
For Windows, replace ./gradlew with gradlew.bat. Cleaning may increase the time required for the next build because Gradle may need to rebuild generated outputs.
5.4 Troubleshooting Android Permissions
Some Android features require permissions declared in the manifest and, where applicable, requested at runtime. Examples include camera access, location access, and audio recording.
Check the following:
- Whether the required permission is declared in
AndroidManifest.xml.
- Whether the application requests runtime permission when required.
- Whether the user has denied or revoked the permission.
- Whether the feature is supported by the device.
- Whether Android-version-specific restrictions apply.
Do not assume that declaring a permission automatically grants access. The application should handle denied permissions and explain why access is needed when appropriate.
6. Troubleshooting Common iOS Issues
iOS builds depend on Xcode, the iOS SDK, signing configuration, CocoaPods where required, and native project settings. A project that runs on Android may still fail to build or execute on iOS because the native toolchains are different.
6.1 Common iOS Build Problems
| Problem |
Possible Cause |
Solution |
| Xcode compilation error |
Incompatible code, SDK, or build settings. |
Review the first relevant compiler error and dependency requirements. |
| Pod installation failure |
Incompatible pod dependencies or CocoaPods configuration. |
Follow the package instructions and inspect the Podfile and lockfile. |
| Signing failure |
Invalid team, certificate, provisioning profile, or bundle identifier. |
Review the signing settings in Xcode. |
| Missing native module |
Dependency not linked or native configuration incomplete. |
Verify installation instructions and rebuild the iOS application. |
| App crashes at launch |
Native initialization failure or runtime exception. |
Inspect the Xcode debug console and device logs. |
| Permission prompt missing |
Required privacy description or permission request is missing. |
Check the relevant Info.plist entries and authorization flow. |
6.2 Inspecting iOS Dependencies
For projects using CocoaPods, navigate to the iOS directory and install dependencies according to the project's setup.
cd ios
pod install
cd ..
If a dependency changes its native configuration, review the package's installation instructions before rebuilding. Avoid deleting lockfiles or changing dependency versions without understanding the consequences.
6.3 Troubleshooting iOS Permissions
iOS requires appropriate privacy usage descriptions for certain sensitive resources. Depending on the feature, these descriptions may be configured in the application's Info.plist.
For example, a camera feature may require a camera usage description. The exact key and authorization flow depend on the capability and library being used.
- Confirm the required privacy description exists.
- Check whether the application requests permission at the correct time.
- Test first-time authorization and denied-permission scenarios.
- Handle restricted or unavailable device capabilities.
- Verify the behavior after permission settings change.
7. Troubleshooting JavaScript and React Native Runtime Errors
Some problems affect both Android and iOS because they originate in shared JavaScript or TypeScript code. Examples include undefined values, incorrect API responses, invalid component props, state-management errors, and unhandled Promise rejections.
7.1 Reading Error Messages
When an application fails, begin with the earliest meaningful error message. Later errors may be consequences of the original problem.
- Reproduce the issue using the same steps.
- Read the error message and identify the relevant file or function.
- Inspect the stack trace.
- Check the values passed into the failing function.
- Determine whether the issue occurs on one platform or both.
- Create a small test that reproduces the problem.
- Fix the cause and run the relevant test suite.
7.2 Handling Undefined API Data
Consider a component that expects a user's name from an API. If the response does not contain the expected property, directly accessing it may cause incorrect UI behavior.
export function getDisplayName(user) {
if (!user || typeof user.name !== 'string' || !user.name.trim()) {
return 'Guest';
}
return user.name;
}
Tests can verify normal and unexpected inputs:
import { getDisplayName } from './userUtils';
describe('getDisplayName', () => {
test('returns the user's name', () => {
expect(getDisplayName({ name: 'Asha' })).toBe('Asha');
});
test('returns Guest when the user is missing', () => {
expect(getDisplayName(null)).toBe('Guest');
});
test('returns Guest when the name is empty', () => {
expect(getDisplayName({ name: ' ' })).toBe('Guest');
});
test('returns Guest when the name is not a string', () => {
expect(getDisplayName({ name: 42 })).toBe('Guest');
});
});
Defensive programming is especially valuable when data comes from external services. Validate response shapes and avoid assuming that all server responses are complete.
7.3 Debugging Asynchronous Errors
Unhandled Promise rejections and race conditions may occur when API requests fail or finish after a component's state has changed.
- Use
try/catch around awaited operations when error handling is needed.
- Show meaningful loading and error states.
- Cancel requests where supported and appropriate.
- Avoid updating state after a component has unmounted.
- Test both resolved and rejected Promises.
8. Troubleshooting Layout, Keyboard, and Safe Area Issues
Layout problems can differ between platforms because of screen dimensions, system bars, safe areas, font metrics, and keyboard behavior.
8.1 Safe Area Problems
On devices with notches, rounded corners, or home indicators, important content may overlap system UI if safe areas are not handled properly.
Useful practices include:
- Use an appropriate safe-area solution for the project.
- Test screens on devices with different screen shapes.
- Check top and bottom insets.
- Avoid relying on fixed offsets that only work on one device.
- Test both portrait and landscape layouts when supported.
8.2 Keyboard Overlapping Form Fields
A keyboard may cover input fields or action buttons, particularly on screens with long forms.
React Native provides KeyboardAvoidingView, which can help adjust the layout when the keyboard appears.
import React from 'react';
import {
KeyboardAvoidingView,
Platform,
TextInput,
View
} from 'react-native';
export default function LoginForm() {
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<View style={{ padding: 16 }}>
<TextInput placeholder="Email" />
<TextInput
placeholder="Password"
secureTextEntry
/>
</View>
</KeyboardAvoidingView>
);
}
This is an illustrative starting point. The appropriate behavior and offsets depend on the screen structure, navigation header, Android window configuration, and keyboard implementation. Test on both operating systems rather than assuming one configuration works everywhere.
8.3 Font and Text Layout Problems
- Use flexible layouts instead of relying on fixed text widths.
- Test long strings and translated content.
- Check line wrapping and truncation.
- Test accessibility font scaling.
- Verify that custom fonts are bundled and loaded correctly.
- Check whether text containers behave differently across platforms.
9. Testing Platform-Specific Logic with Jest
Jest can test JavaScript branches that depend on the platform. However, a Jest test running in a Node-based environment does not reproduce the complete Android or iOS runtime. It verifies the logic selected for the configured platform value.
9.1 Create a Platform Utility
Create platformUtils.js.
import { Platform } from 'react-native';
export function getPlatformMessage() {
if (Platform.OS === 'ios') {
return 'Running on iOS';
}
if (Platform.OS === 'android') {
return 'Running on Android';
}
return 'Running on another platform';
}
9.2 Write a Basic Platform Test
import { getPlatformMessage } from './platformUtils';
import { Platform } from 'react-native';
test('returns a message for the current platform', () => {
const message = getPlatformMessage();
if (Platform.OS === 'ios') {
expect(message).toBe('Running on iOS');
} else if (Platform.OS === 'android') {
expect(message).toBe('Running on Android');
} else {
expect(message).toBe('Running on another platform');
}
});
This verifies the branch selected by the current test environment. It does not independently test both Android and iOS branches in a single run.
9.3 Test Each Branch Explicitly
For greater control, move platform-dependent logic into a pure function that accepts the platform as an argument.
export function getMessageForPlatform(platform) {
if (platform === 'ios') {
return 'Running on iOS';
}
if (platform === 'android') {
return 'Running on Android';
}
return 'Running on another platform';
}
Tests:
import { getMessageForPlatform } from './platformUtils';
describe('getMessageForPlatform', () => {
test('returns the iOS message', () => {
expect(getMessageForPlatform('ios')).toBe('Running on iOS');
});
test('returns the Android message', () => {
expect(getMessageForPlatform('android')).toBe('Running on Android');
});
test('returns the fallback message', () => {
expect(getMessageForPlatform('web')).toBe('Running on another platform');
});
});
This design makes the logic easy to test because each input is controlled explicitly. Keep the platform detection at the application boundary and pass the resulting value into the pure function.
9.4 Testing Platform.select()
When a component uses Platform.select(), test the platform-independent logic where possible and use appropriate integration or platform-specific test configurations to validate actual platform behavior.
Avoid mutating internal React Native module properties unnecessarily. Such changes can create brittle tests and may not reproduce how Metro selects platform-specific files.
10. Testing Platform-Specific Files
React Native supports platform-specific file extensions such as .android.js and .ios.js. These files can contain different implementations behind a common import path.
10.1 Example Structure
src/
components/
StatusMessage.android.js
StatusMessage.ios.js
StatusMessage.test.js
The Android implementation may use Android-specific behavior, while the iOS implementation uses an iOS-specific approach.
10.2 Testing Strategy
- Test shared business logic using platform-independent unit tests.
- Test each platform-specific implementation with the relevant Jest resolver configuration.
- Verify platform file resolution in the actual build configuration.
- Use Android and iOS simulators or devices to validate native behavior.
- Avoid assuming that one test run automatically covers both platform-specific files.
The exact test-file naming and resolution rules depend on the Jest preset, test environment, and project configuration. Configure separate test projects or platform-specific test runs if both implementations need independent coverage.
11. Introduction to Test Coverage
Test coverage measures which parts of the source code are executed while automated tests run. Jest can collect coverage information and produce reports that help identify untested functions, statements, lines, and branches.
Coverage is useful for locating gaps in a test suite, but it does not guarantee that the application is correct. A line may execute without its output being meaningfully checked.
11.1 Why Test Coverage Matters
- Identifies source code that has not been exercised by tests.
- Helps reveal missing conditional branches.
- Encourages systematic testing of edge cases.
- Supports regression prevention during refactoring.
- Helps teams monitor test-suite progress.
- Can enforce minimum testing requirements in continuous integration.
11.2 Types of Coverage
| Coverage Type |
Meaning |
Example |
| Statement Coverage |
Percentage of executable statements that ran. |
Checking whether an error-handling statement executed. |
| Line Coverage |
Percentage of executable source lines that ran. |
Checking whether a return statement was reached. |
| Function Coverage |
Percentage of functions called during testing. |
Checking whether each utility function was invoked. |
| Branch Coverage |
Percentage of decision outcomes exercised. |
Testing both true and false outcomes of an if statement. |
12. Generating a Jest Coverage Report
Jest can collect coverage while executing the test suite. Coverage collection can increase execution time, so it is often enabled for dedicated coverage runs or configured in continuous integration.
12.1 Run Coverage from the Command Line
npm test -- --coverage
If the project defines a coverage script in package.json, you can use:
npm run test:coverage
12.2 Example Coverage Script
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage"
}
}
12.3 Example Jest Configuration
A configuration may specify which source files should be included in the coverage report.
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/__tests__/**'
],
coverageReporters: [
'text',
'html',
'lcov'
]
};
Adjust the patterns to match the project. In particular, ensure that the coverage configuration does not accidentally exclude production files that should be tested. See the official Jest configuration documentation for additional options.
12.4 Understanding Coverage Output
A coverage report may display the following information:
| Column |
Meaning |
| File |
The source file being measured. |
| % Stmts |
Statement coverage percentage. |
| % Branch |
Branch coverage percentage. |
| % Funcs |
Function coverage percentage. |
| % Lines |
Line coverage percentage. |
| Uncovered Lines |
Line numbers that were not executed. |
The HTML coverage report can help locate uncovered code. Review the highlighted lines and determine which meaningful scenarios are missing from the test suite.
13. Improving Statement and Line Coverage
Statement and line coverage improve when tests execute code paths that were previously not reached. The goal should be to verify meaningful behavior rather than execute code without assertions.
13.1 Example Function
export function calculateDiscount(price, discount) {
if (price < 0) {
throw new Error('Price cannot be negative');
}
if (discount < 0 || discount > 100) {
throw new Error('Invalid discount');
}
return price - (price * discount) / 100;
}
13.2 Write Tests for Each Important Outcome
import { calculateDiscount } from './discount';
describe('calculateDiscount', () => {
test('calculates a valid discount', () => {
expect(calculateDiscount(200, 10)).toBe(180);
});
test('accepts a zero discount', () => {
expect(calculateDiscount(200, 0)).toBe(200);
});
test('rejects a negative price', () => {
expect(() => calculateDiscount(-10, 10))
.toThrow('Price cannot be negative');
});
test('rejects a negative discount', () => {
expect(() => calculateDiscount(200, -1))
.toThrow('Invalid discount');
});
test('rejects a discount greater than 100', () => {
expect(() => calculateDiscount(200, 101))
.toThrow('Invalid discount');
});
});
These tests exercise the successful calculation and the validation errors. They also demonstrate why testing only the normal case can leave important code paths untested.
14. Improving Branch Coverage
Branch coverage focuses on different outcomes of conditional expressions. A function can have high line coverage while still missing important decision outcomes.
14.1 Example of Conditional Logic
export function getAccessMessage(isLoggedIn, isAdmin) {
if (!isLoggedIn) {
return 'Please log in';
}
if (isAdmin) {
return 'Admin access granted';
}
return 'User access granted';
}
14.2 Cover Every Branch
import { getAccessMessage } from './access';
describe('getAccessMessage', () => {
test('requests login for a logged-out user', () => {
expect(getAccessMessage(false, false)).toBe('Please log in');
});
test('grants admin access to an administrator', () => {
expect(getAccessMessage(true, true)).toBe('Admin access granted');
});
test('grants regular access to a logged-in user', () => {
expect(getAccessMessage(true, false)).toBe('User access granted');
});
});
These tests exercise the logged-out, administrator, and regular-user outcomes. This is more useful than testing only the branch that returns a successful result.
14.3 Common Areas Where Branches Are Missed
- Conditional rendering of loading, success, and error states.
- Fallback values for missing API data.
- Validation of empty and invalid input.
- Permission granted versus permission denied.
- Platform-specific logic for Android and iOS.
- Feature flags that enable or disable functionality.
- Retry logic and maximum-attempt conditions.
15. Improving Function Coverage
Function coverage indicates which functions have been called during the test run. Functions may remain uncovered because they are rarely used, are only called on errors, or are not part of the existing test scenarios.
15.1 Example Utility Module
export function formatName(firstName, lastName) {
return `${firstName} ${lastName}`.trim();
}
export function getInitials(firstName, lastName) {
return `${firstName[0] || ''}${lastName[0] || ''}`.toUpperCase();
}
15.2 Test Each Function
import { formatName, getInitials } from './nameUtils';
describe('name utilities', () => {
test('formats a full name', () => {
expect(formatName('Asha', 'Patel')).toBe('Asha Patel');
});
test('returns uppercase initials', () => {
expect(getInitials('Asha', 'Patel')).toBe('AP');
});
test('handles an empty first name', () => {
expect(getInitials('', 'Patel')).toBe('P');
});
});
These tests call both exported functions and check important results. Depending on the input contract, add validation for missing or invalid values as well.
16. Testing Loading, Success, and Error States
React Native screens often have several states depending on network requests. Testing only the successful state leaves potential bugs in loading indicators and error handling.
16.1 Common UI States
| State |
Expected Behavior |
| Loading |
Displays a loading indicator or message while data is being fetched. |
| Success |
Displays the retrieved data. |
| Empty |
Displays an appropriate message when no records are available. |
| Error |
Displays a useful error message or retry option. |
| Retry |
Attempts the operation again after the user requests it. |
16.2 Example Component
import React, { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
export default function ProductScreen({ loadProducts }) {
const [status, setStatus] = useState('loading');
const [products, setProducts] = useState([]);
useEffect(() => {
let active = true;
loadProducts()
.then(data => {
if (active) {
setProducts(data);
setStatus(data.length ? 'success' : 'empty');
}
})
.catch(() => {
if (active) {
setStatus('error');
}
});
return () => {
active = false;
};
}, [loadProducts]);
if (status === 'loading') {
return <Text>Loading products...</Text>;
}
if (status === 'error') {
return <Text>Unable to load products</Text>;
}
if (status === 'empty') {
return <Text>No products available</Text>;
}
return (
<View>
{products.map(product => (
<Text key={product.id}>{product.name}</Text>
))}
</View>
);
}
The dependency is injected through loadProducts, making the component easier to test. In a real application, ensure the function has a stable reference or manage the effect appropriately to avoid repeated requests.
16.3 Test the Error State
import React from 'react';
import { render, screen } from '@testing-library/react-native';
import ProductScreen from './ProductScreen';
test('displays an error when loading fails', async () => {
const loadProducts = jest.fn().mockRejectedValue(
new Error('Network error')
);
render(<ProductScreen loadProducts={loadProducts} />);
expect(
await screen.findByText('Unable to load products')
).toBeTruthy();
});
16.4 Test the Empty State
test('displays an empty state when no products are returned', async () => {
const loadProducts = jest.fn().mockResolvedValue([]);
render(<ProductScreen loadProducts={loadProducts} />);
expect(
await screen.findByText('No products available')
).toBeTruthy();
});
Testing each state improves branch coverage and verifies how the application responds to different real-world outcomes.
17. Setting Coverage Thresholds
Jest can enforce minimum coverage requirements. If a configured threshold is not met, the test run fails. This can help prevent coverage from dropping significantly as new code is introduced.
17.1 Example Configuration
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts'
],
coverageThreshold: {
global: {
statements: 80,
branches: 75,
functions: 80,
lines: 80
}
}
};
The percentages are illustrative targets, not universal requirements. Set thresholds that reflect the project's risk, maturity, and testing strategy.
17.2 Benefits of Coverage Thresholds
- Provide measurable minimum expectations for automated tests.
- Help prevent unnoticed declines in coverage.
- Encourage developers to test new code paths.
- Support consistent quality checks in CI pipelines.
17.3 Limitations of Coverage Thresholds
A threshold can be satisfied by tests that execute code without checking important behavior. Teams should review assertion quality, error handling, and edge-case coverage instead of treating a percentage as proof of correctness.
18. Troubleshooting Coverage Problems
| Problem |
Possible Cause |
Solution |
| Coverage shows 0% |
Incorrect source patterns or no matching files. |
Review collectCoverageFrom and project paths. |
| Some source files are missing |
Files are excluded or do not match the configured patterns. |
Review include and exclude patterns. |
| Branch coverage is low |
Only one outcome of conditional logic is tested. |
Add tests for the remaining decision outcomes. |
| Function coverage is low |
Some functions are never called by the tests. |
Identify untested functions and add meaningful cases. |
| Coverage drops after refactoring |
Tests no longer reach changed code paths. |
Update the tests to reflect the new behavior and structure. |
| Coverage report is slow |
Coverage instrumentation adds execution overhead. |
Run coverage in a dedicated workflow when appropriate. |
| Coverage is high but bugs remain |
Tests execute code without asserting meaningful outcomes. |
Improve assertions and test error and boundary scenarios. |
19. Combining Platform Testing and Coverage
Platform-specific testing and code coverage should be planned together. A shared test suite may cover the common business logic while platform-specific tests verify behavior unique to Android or iOS.
19.1 Recommended Testing Layers
| Layer |
What to Test |
Typical Tools |
| Unit Tests |
Pure functions, validation, calculations, and platform-independent logic. |
Jest |
| Component Tests |
Rendering, user interactions, loading states, and error messages. |
Jest and React Native Testing Library |
| Integration Tests |
Interaction between services, navigation, and components. |
Jest and React Native Testing Library |
| Native Integration Checks |
Permissions, native modules, build configuration, and OS behavior. |
Android tooling and Xcode |
| End-to-End Tests |
Complete user journeys on a device or simulator. |
Detox, Maestro, or another suitable E2E tool |
19.2 Recommended Platform Test Matrix
For important application features, maintain a list of platform and scenario combinations that must be checked.
| Feature |
Android |
iOS |
| Login form validation |
Unit and component tests |
Shared unit and component tests |
| Camera permission |
Permission and device testing |
Permission and device testing |
| Navigation and Back behavior |
Android navigation testing |
Navigation and swipe-back testing |
| Keyboard layout |
Test representative devices |
Test representative devices |
| API error handling |
Shared automated tests |
Shared automated tests |
| Native module integration |
Android build and runtime checks |
iOS build and runtime checks |
Shared JavaScript tests can run once for common logic, but platform-specific native behavior still needs appropriate platform-level validation.
20. Debugging Tools and Techniques
20.1 React Native Developer Tools
Use the development tools supported by your React Native version to inspect component behavior, debug JavaScript, and investigate rendering or state problems.
20.2 Android Logcat
Android Logcat displays system and application logs. It can help diagnose crashes, native exceptions, permission errors, and failures during application initialization.
20.3 Xcode Debug Console
The Xcode debugger and device logs help investigate iOS crashes, native exceptions, dependency initialization problems, and build or runtime errors.
20.4 Network Inspection
Inspect API request URLs, status codes, response payloads, and error messages. Avoid logging passwords, authorization tokens, or sensitive personal information.
20.5 Reproducing Issues Systematically
- Record the platform, OS version, device model, and application build.
- Write down the steps needed to reproduce the issue.
- Determine whether it occurs consistently or intermittently.
- Compare behavior across Android and iOS.
- Check logs and relevant configuration files.
- Create a focused automated test when the problem is reproducible in JavaScript.
- Verify the fix on the affected platform and run regression tests.
21. Continuous Integration for React Native Tests
Continuous integration (CI) runs automated checks when code changes are submitted. It can run unit tests, collect coverage, enforce thresholds, and build platform-specific applications.
21.1 Typical CI Workflow
- Install project dependencies.
- Run linting and static checks.
- Run Jest tests.
- Generate the coverage report.
- Check coverage thresholds.
- Build Android and iOS applications in the appropriate environments.
- Run additional device or end-to-end tests when configured.
21.2 Example Commands for CI
npm ci
npm test -- --coverage
These commands install dependencies from the lockfile and execute tests with coverage. Actual CI configuration should also account for the required Node.js version, operating system, native toolchains, environment variables, and any platform-specific build steps.
21.3 Benefits of CI Testing
- Detects regressions before changes are merged.
- Ensures tests run in a repeatable environment.
- Tracks coverage changes over time.
- Helps detect platform-specific build failures.
- Reduces dependence on manual testing alone.
22. Practical Project: Platform-Aware Greeting Utility
In this exercise, create a utility that returns a platform-specific message and write tests for every supported outcome. Then generate a coverage report to confirm that the decision branches are exercised.
22.1 Create the Utility
Create platformGreeting.js.
export function getGreeting(platform, isLoggedIn) {
if (!isLoggedIn) {
return 'Please log in';
}
if (platform === 'ios') {
return 'Welcome to iOS';
}
if (platform === 'android') {
return 'Welcome to Android';
}
return 'Welcome to the application';
}
22.2 Write the Tests
Create platformGreeting.test.js.
import { getGreeting } from './platformGreeting';
describe('getGreeting', () => {
test('asks logged-out users to log in', () => {
expect(getGreeting('ios', false)).toBe('Please log in');
});
test('returns the iOS greeting', () => {
expect(getGreeting('ios', true)).toBe('Welcome to iOS');
});
test('returns the Android greeting', () => {
expect(getGreeting('android', true)).toBe('Welcome to Android');
});
test('returns the fallback greeting', () => {
expect(getGreeting('web', true)).toBe('Welcome to the application');
});
});
22.3 Generate Coverage
npm test -- --coverage
Review the report and confirm that the tests execute the logged-out condition, both platform-specific conditions, and the fallback branch.
22.4 Project Extension Tasks
- Add a validation rule for an unsupported platform value.
- Build a React Native component that displays the greeting.
- Test the component using React Native Testing Library.
- Test a loading state and an error state in a separate API-driven component.
- Configure a minimum coverage threshold in Jest.
- Document one platform-specific issue and the steps used to reproduce and fix it.
23. Best Practices Checklist
- Use platform detection only where the behavior genuinely differs.
- Prefer shared code for common business logic.
- Separate complex Android and iOS implementations into platform-specific files.
- Reproduce build errors before changing configuration.
- Check permission declarations and authorization flows on both platforms.
- Test safe areas, keyboard behavior, and screen sizes on representative devices.
- Write unit tests for pure logic and component tests for observable UI behavior.
- Test every meaningful conditional branch, including error and fallback paths.
- Use coverage reports to identify missing tests rather than as the sole measure of quality.
- Keep tests independent and avoid relying on execution order.
- Run regression tests after fixing platform-specific bugs.
- Use CI to run automated tests and platform-specific builds.
- Verify native behavior on simulators or physical devices when required.
- Never expose production credentials or sensitive user data in logs or test fixtures.
24. Interview Questions and Answers
Q1. What is a platform-specific issue in React Native?
It is a bug or behavior that occurs on one operating system or device configuration but may not occur on another, often because of differences in native APIs, permissions, layout, or dependencies.
Q2. How do you detect the current platform?
Use the Platform module from React Native, particularly Platform.OS, to identify the current platform and select suitable behavior.
Q3. What is Platform.select()?
Platform.select() chooses a value from an object according to the current platform. It is useful for platform-specific styles, values, or implementations.
Q4. What are platform-specific files?
Files such as Component.android.js and Component.ios.js allow React Native's module resolution to select different implementations for different platforms.
Q5. How do you troubleshoot an Android build failure?
Inspect the Gradle error, check SDK and JDK compatibility, verify native dependency configuration, and use Android tooling to reproduce the problem.
Q6. How do you troubleshoot an iOS build failure?
Inspect Xcode errors, verify native dependencies and CocoaPods configuration, and check signing settings and required privacy configuration.
Q7. What is statement coverage?
Statement coverage measures the proportion of executable statements reached during automated test execution.
Q8. What is branch coverage?
Branch coverage measures how many possible decision outcomes have been exercised by tests.
Q9. How do you generate a Jest coverage report?
Run npm test -- --coverage or use a project script that executes Jest with the coverage option.
Q10. Does 100% code coverage guarantee a bug-free application?
No. Coverage measures code execution, not the correctness or completeness of assertions. Tests must verify meaningful outcomes and important edge cases.
Q11. How do you test platform-dependent logic?
Where possible, isolate the logic in a pure function that accepts the platform as an argument, then test each supported value independently. Also validate native behavior on the relevant platform.
Q12. What is a coverage threshold?
A coverage threshold is a configured minimum for metrics such as statements, branches, functions, and lines. Jest can fail the test run when the required minimum is not met.
Q13. Why is branch coverage important?
It helps reveal missing tests for alternative outcomes, such as success and failure, permission granted and denied, or Android and iOS behavior.
Q14. How can platform-specific bugs be prevented?
Use shared logic where appropriate, maintain focused platform-specific implementations, test both platforms, inspect native logs, and include regression checks in CI.
Q15. What is the role of continuous integration?
CI automatically runs tests and other quality checks when code changes are submitted, helping detect regressions and build failures earlier.
25. Quick Revision Summary
- Platform-specific bugs arise from differences between operating systems, devices, and native dependencies.
Platform.OS and Platform.select() help manage small platform differences.
- Platform-specific file extensions are useful for significantly different implementations.
- Android troubleshooting often involves Gradle, the SDK, JDK compatibility, permissions, and Logcat.
- iOS troubleshooting often involves Xcode, CocoaPods, signing, privacy descriptions, and device logs.
- Jest can test platform-dependent JavaScript logic, but it does not replace native device testing.
- Statement, line, function, and branch coverage measure different aspects of code execution.
- Coverage reports identify untested areas and help guide additional test cases.
- Meaningful assertions, edge cases, and error-path testing are more valuable than coverage percentages alone.
- CI helps automate tests, coverage checks, and platform-specific build validation.
26. Conclusion
Troubleshooting platform-specific issues and improving test coverage are essential practices in React Native development. Android and iOS share much of the application code, but native configuration, permissions, layout behavior, and device capabilities can produce different results.
A structured troubleshooting process helps developers reproduce errors, inspect logs, identify the underlying cause, and verify fixes. Jest and React Native Testing Library support automated testing of business logic, platform-dependent branches, UI states, and error handling. Coverage reports reveal areas that need further testing, while simulators, physical devices, and integration tests help validate actual native behavior.
The most effective approach combines clear platform-specific code, meaningful automated tests, realistic device validation, and continuous integration. This helps produce React Native applications that are more reliable, maintainable, and consistent across platforms.
Course Resources: React Native Training | Register for a Course Demo | JustAcademy Official Website