Course outline

React Native / Asynchronous and data ownership / Lesson 12

Model, component, and target test evidence

Choose deterministic checks for the actual uncertainty and avoid turning a JavaScript pass into a native-runtime claim.

About 4 min readContent reviewed
On this page

Lesson goals

  • Test the boundary where behavior can fail
  • Use assertions that distinguish plausible mistakes
  • Resolve races explicitly instead of sleeping

Test the boundary where behavior can fail

A pure function test can establish input normalization or immutable transitions. A configured component test exercises React descriptions and public interactions. A target end-to-end test crosses native rendering, system integration, and the selected application host. These provide different evidence and cost. Start with the smallest reliable test that exposes the suspected defect, then add broader checks for contracts that truly cross those boundaries. A screenshot cannot prove request freshness, and a decoder pass cannot prove an error is spoken with useful context. State which implementation and host the test actually uses.

Use assertions that distinguish plausible mistakes

The first standalone program verifies a selection function against explicit expected snapshots and membership. Freezing the input makes accidental direct mutation visible, while comparing the old and new arrays checks the ownership contract. Do not copy the implementation into a second function and call agreement proof. Choose boundary cases such as removing an existing identifier and adding an absent one. Node’s assertion library throws on failure; the success line is reached only after all checks pass. This is a local model test and does not mount a React component.

javascript Standalone JavaScript model with synthetic local inputs.
const assert = require('node:assert/strict');
function toggle(selected, id) {
  return selected.includes(id) ? selected.filter(value => value !== id) : [...selected, id];
}
const before = Object.freeze(['w-7']);
const added = toggle(before, 'w-9');
assert.deepEqual(before, ['w-7']);
assert.deepEqual(added, ['w-7', 'w-9']);
assert.notEqual(added, before);
assert.deepEqual(toggle(added, 'w-7'), ['w-9']);
console.log('selection assertions passed');
Expected output
selection assertions passed

The test checks unchanged source, a new reference, and both membership transitions. It establishes no native rerender behavior.

Resolve races explicitly instead of sleeping

The second program uses deferred promises to choose completion order. It starts A then B, resolves B first, and verifies A cannot overwrite it when A finishes. No timing delay or external service is needed. A fake must resolve every continuation it owns and propagate failures so a hanging test does not hide the original problem. Extend this pattern with stale rejection and session invalidation when those policies are part of the model. The example uses Node scheduling only and cannot claim React Effect cleanup or native transport cancellation was tested.

javascript Standalone JavaScript model with synthetic local inputs.
const assert = require('node:assert/strict');
function deferred() {
  let resolve;
  const promise = new Promise(done => { resolve = done; });
  return {promise, resolve};
}
async function testOrder() {
  let generation = 0;
  let visible = null;
  function load(promise) {
    const ticket = ++generation;
    return promise.then(value => { if (ticket === generation) visible = value; });
  }
  const a = deferred(), b = deferred();
  const waitingA = load(a.promise), waitingB = load(b.promise);
  b.resolve('Rivers'); await waitingB;
  a.resolve('Maps'); await waitingA;
  assert.equal(visible, 'Rivers');
  console.log('completion-order assertion passed');
}
testOrder().catch(error => { console.error(error.message); process.exitCode = 1; });
Expected output
completion-order assertion passed

The ordering is controlled by promise resolution, not wall-clock assumptions. All work remains in memory.

Component tests need a compatible renderer and semantics

A component test should activate a publicly labeled control and inspect the accepted interface rather than call a private handler directly. Match the test renderer, React, React Native, and testing-library versions selected by the project; test APIs and peer dependencies can change. A Node model with no renderer cannot establish role queries, accessibility grouping, focus, or a committed native tree. Snapshot output is useful only with a reviewed purpose and does not replace meaningful interaction assertions. Keep mocked native modules narrow so a fake does not silently turn every platform failure into success.

Report validation with reproducible scope

Record runtime, package lock, build mode, target, fixtures, and executed checks when reporting meaningful results. For visual comparisons, also control locale, text scale, fonts, and device configuration before accepting a changed baseline. Native permission dialogs, keyboard behavior, linking, persistence, and frame pacing require target evidence. This course executes only standalone JavaScript models under the available Node runtime. React, React Native, JSX parsing, renderer tests, Hermes, device builds, and native integration remain unexecuted. A finite assertion suite supports the tested policy but does not prove all inputs or certify an application release.

Common errors and fixes

An async test passes by timing luck

Why it happens
Arbitrary sleeps replaced controlled completion.
How to fix it
Use deferred responses and explicit awaited checkpoints.
Prevent it next time
Exercise the dangerous order directly.

A Node assertion is reported as a component test

Why it happens
No renderer or actual component interaction was exercised.
How to fix it
Label model evidence and keep component checks separate.
Prevent it next time
Name the host and implementation in the report.

A changed screenshot is accepted without review

Why it happens
Configuration drift or a real regression was ignored.
How to fix it
Inspect the difference under controlled rendering inputs.
Prevent it next time
Document why a visual baseline changes.

Check your understanding

1. What does the deferred-promise test prove about request cancellation?

Show worked solution

It proves only that the modeled publication gate rejects the older result under the chosen completion order. Both promises still complete. Transport cancellation, resource release, Effect cleanup, and server outcomes require separate contracts and evidence.

2. Why is direct invocation of a private button handler insufficient for an interaction claim?

Show worked solution

It bypasses whether the user can find and activate the control and whether the resulting state is presented correctly. Keep that as a model-level check, then use a compatible renderer or target to exercise public semantics, activation, and accepted results.

Key takeaways

  • Test the boundary where behavior can fail
  • Report validation with reproducible scope

Official references