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
1
Create a test file
Add a
*.test.ts file under test/unit/.2
Import the class and the mock
Import
SSHClient from ../../src/sshclient and the helpers from
../mocks/react-native and ./support.3
Reset between tests
Call
resetMocks() in a beforeEach so listeners and mock state do not leak
between cases.4
Drive a native call
Set an implementation with
resolveNative(...) or rejectNative(...), invoke
the method, and assert the resolved value, the rejection, and the arguments
forwarded to the native mock.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.