Testing WebView logic
If your app uses react-native-webview to run HTML + JavaScript, the hardest part to test is usually the logic inside — the scripts that query or mutate the DOM before passing a result back via postMessage.
Testing the WebView component itself (does onLoad fire? does onMessage receive data?) is a UI concern. But the logic embedded in injectedJavaScript is pure JS + DOM, and react-native-nitro-jsdom happens to be a good fit for running it in isolation.
The problem with alternatives
Jest (Node.js) cannot run native modules at all, so any native-backed sandbox is off the table there.
Testing a real WebView with a native runner works, but comes with cost: each WebView instance takes 500ms–2s to load, you can only observe state through postMessage, and teardown between tests is fragile.
The approach
Use react-native-nitro-jsdom to run the embedded JS directly, without any WebView:
import { JSDOM } from 'react-native-nitro-jsdom'
// the same HTML your WebView would receive
const dom = JSDOM.create(`
<html>
<body>
<div id="price" data-original="100">100</div>
</body>
</html>
`)
// the same script you'd pass as injectedJavaScript
const result = await dom.evaluate(`
const el = document.getElementById('price')
const original = parseFloat(el.dataset.original)
el.textContent = String((original * 0.9).toFixed(2))
el.textContent
`)
// assert the output without a postMessage round-trip
expect(result).toBe('90.00')
dom.dispose()
Each test gets a clean, isolated DOM that starts and tears down in milliseconds.
With react-native-harness
react-native-harness by Callstack runs Jest-style tests (describe / it / expect) in a real native environment — simulator, emulator, or physical device. That makes it compatible with Nitro Modules, including this library.
import { describe, it, expect, beforeEach } from 'react-native-harness'
import { JSDOM } from 'react-native-nitro-jsdom'
describe('price badge script', () => {
let dom: ReturnType<typeof JSDOM.create>
beforeEach(() => {
dom = JSDOM.create(`
<html>
<body>
<div id="price" data-original="100">100</div>
</body>
</html>
`)
})
afterEach(() => {
dom.dispose()
})
it('applies 10% discount', async () => {
const result = await dom.evaluate(`
const el = document.getElementById('price')
el.textContent = String((parseFloat(el.dataset.original) * 0.9).toFixed(2))
el.textContent
`)
expect(result).toBe('90.00')
})
it('preserves original value in data attribute', async () => {
const result = await dom.evaluate(
`document.getElementById('price').dataset.original`
)
expect(result).toBe('100')
})
})
What this covers and what it does not
This approach is well-suited for testing the JavaScript logic that runs inside a WebView: DOM queries, mutations, computed values, script side-effects.
It does not replace component-level tests. If you need to verify that onMessage fires, that injectedJavaScriptBeforeContentLoaded runs at the right time, or that the WebView mounts and renders — use react-native-harness with a real WebView component instead.