Unit tests
Fast Vitest tests for the TypeScript API in
src/sshclient.ts, with the native bridge mocked. These are the confidence core.Integration fixture
A Dockerized OpenSSH server plus a Node smoke test that proves real SSH and SFTP work against a predictable target.
Unit tests
The unit suite drives theSSHClient class in a plain Node process. It covers the
connection factories, platform dispatch, command execution, the shell lifecycle,
the full SFTP surface, transfer counters, event routing, and disconnect cleanup.
Run them:
How the react-native mock works
src/sshclient.ts imports from react-native, a package that only resolves inside
a React Native runtime. For unit tests, vitest.config.ts aliases react-native
to a hand-written stub at test/mocks/react-native.ts.
The stub exposes exactly what the class touches:
NativeModules.RNSSHClient— a mock function per native method, so each test steers a call down its success or error path.NativeEventEmitter/DeviceEventEmitter— backed by a shared listener store so a test can emit native events and assert routing.Platform— a mutableOSyou can flip betweeniosandandroid.
The real native bridge always invokes callbacks asynchronously. The test helpers in
test/unit/support.ts mirror this by deferring callbacks onto a microtask, which
keeps patterns like const client = new SSHClient(..., cb => resolve(client))
working correctly.Adding a unit test
Import the class and the mock
Import
SSHClient from ../../src/sshclient and the helpers from
../mocks/react-native and ./support.Reset between tests
Call
resetMocks() in a beforeEach so listeners and mock state do not leak
between cases.Coverage expectations
Coverage runs on the v8 provider and enforces global thresholds set invitest.config.ts. A commit or CI run fails if coverage drops below the floor, so
new code should arrive with tests. Raise the thresholds as coverage grows rather
than lowering them to fit a change.
Integration fixture
The integration harness lives intest/integration/. It brings up a disposable
OpenSSH server with Docker and runs a Node smoke test that
connects with a standalone client (ssh2), runs a
command, and completes an SFTP write, list, and read round-trip.
The all-in-one script starts the fixture, runs the test, and tears it down:
Running
npm run test:integration without the fixture up makes the smoke test
retry briefly and then fail with an actionable message pointing you at the command
above, rather than a raw connection error.SSH_HOST, SSH_PORT, SSH_USER, SSH_PASSWORD, SSH_REMOTE_DIR).
See test/integration/README.md for the full table.
Continuous integration
- Unit tests run in the
Compile packageworkflow on every push and pull request, across Node 22, 24, and 26, and upload a coverage report. - Integration tests run in a separate
Integration testsworkflow that stands up the OpenSSH fixture, so container flakiness never blocks unit feedback.
Future work
Two tiers are intentionally deferred. They require a native runtime that Node cannot provide, and each deserves its own design pass.Native unit tests
Native unit tests
Test the platform bridges directly: iOS (
ios/RNSSHClient.m, SSHClient.m) with
XCTest, and Android (RNSshClientModule.java) with JUnit. This would cover logic
that lives only in native code, such as JSON serialization of directory listings.Native end-to-end tests
Native end-to-end tests
Add an example app and drive it with Detox or Maestro against the Docker OpenSSH
fixture. This is the only tier that exercises the full stack — JavaScript API,
native bridge, and a real server — end to end.