@rnw-community/react-native-payments
Version:
Accept Payments with Apple Pay and Android Pay using the Payment Request API.
74 lines (64 loc) • 4.12 kB
Markdown
# Unit testing
Without a linked native binary, `NativePayments` (`src/class/native-payments/native-payments.ts`) falls back to a
`Proxy` that throws `The package 'react-native-payments' doesn't seem to be linked` for any required method call
(`show`, `abort`, `complete`, `canMakePayment`, `hasEnrolledInstrument`) — see
[architecture.md](../architecture.md). Mock `react-native`'s `NativeModules.Payments` so the module resolves to
your own fakes instead of falling through to that proxy:
```ts
import { jest } from '@jest/globals';
jest.mock('react-native', () => ({
NativeModules: {
Payments: {
show: jest.fn(() => Promise.resolve(JSON.stringify({ token: {} }))),
abort: jest.fn(() => Promise.resolve()),
canMakePayments: jest.fn(() => Promise.resolve(true)),
hasEnrolledInstrument: jest.fn(() => Promise.resolve(true)),
complete: jest.fn(() => Promise.resolve()),
retry: jest.fn(() => Promise.resolve()),
setActiveEvents: jest.fn(() => Promise.resolve()),
updatePaymentDetails: jest.fn(() => Promise.resolve()),
addListener: jest.fn(),
removeListeners: jest.fn(),
},
},
Platform: { OS: 'ios', select: (specifics: { default: string }) => specifics.default },
TurboModuleRegistry: { get: () => null },
NativeEventEmitter: class {
addListener() {
return { remove: () => undefined };
}
removeAllListeners() {
return undefined;
}
},
}));
```
This works regardless of architecture: `NativePayments` only reaches for the TurboModule path
(`TurboModuleRegistry.get('Payments')`, plural) when `global.__turboModuleProxy` is set, which a Jest run
normally never sets, so it resolves `NativeModules.Payments` instead — mock that key directly rather than
`TurboModuleRegistry.getEnforcing`, which this package never calls. All ten `Spec` members from
`NativePayments.ts` are stubbed, so `retry()` and event registration do not silently no-op in a test that
exercises them. The `NativeEventEmitter` stub is required as soon as `addListener`/`removeListeners` are
present: `getNativePaymentsEventEmitter()` (`src/util/get-native-payments-event-emitter/`) constructs a real
`new NativeEventEmitter(...)` the moment those two methods are defined, and a fully-replaced `react-native`
module has no real `NativeEventEmitter` export to construct — omitting the stub throws
`NativeEventEmitter is not a constructor` from `PaymentRequest.addEventListener(...)`. If Jest fails with
`The package 'react-native-payments' doesn't seem to be linked`, this mock is the fix — see
[#227](https://github.com/rnw-community/rnw-community/issues/227) and
[troubleshooting.md](./troubleshooting.md).
`show`'s fake resolves to `{ token: {} }` rather than `'{}'` because `PaymentRequest.show()` parses the resolved
string straight into `IosPaymentResponse`/`AndroidPaymentResponse` — `'{}'` has no `token` (iOS) or
`paymentMethodData` (Android) key and rejects with `PaymentsError: Failed parsing PaymentRequest details`.
Replace the fake per test with a payload shaped like `IosPKPayment` (iOS) or `AndroidPaymentData` (Android) once
the assertions need real token/address fields.
## Example apps
- **Expo** — the `App` component of the
[react-native-payments-example](../../../react-native-payments-example/readme.md) package, running through its
`apps/expo` target.
- **Bare React Native CLI** — the same `App` component, running through the
[react-native-payments-example](../../../react-native-payments-example/readme.md) package's `apps/bare` target.
## End-to-end verification
Unit tests cover the JS layer at 100%; on-device verification of the event API (sheet opens, shipping/coupon
change round-trip, async `updateWith` completion) runs locally through the Maestro flow suite in
[react-native-payments-example/e2e/readme.md](../../../react-native-payments-example/e2e/readme.md), but is not
yet wired into CI — tracked in [#395](https://github.com/rnw-community/rnw-community/issues/395).