Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 5 additions & 40 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,41 +1,6 @@
.DS_Store

test-app/

*.js
*.js.map
!nativescript.webpack.js
!nativescript.webpack.compat.js
!loaders/unit-test-loader.js

coverage
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
node_modules/
dist/
*.tgz
*.tmp
*.sublime-workspace
tscommand*.tmp.txt
.tscache/

pids
logs
results
scratch/
.idea/
.settings/
.vscode/
test-reports.xml
package-lock.json

npm-debug.log
node_modules
.d.ts

platforms/android/nativescript_unit_test_runner.aar
platforms/android/unit_test_runner.aar
*.tsbuildinfo
.DS_Store
coverage/
19 changes: 0 additions & 19 deletions .npmignore

This file was deleted.

148 changes: 134 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,146 @@
Unit test runner for NativeScript
=================================
# @nativescript/unit-test-runner

Refer to the documentation of NativeScript CLI's `ns test init` command for usage.
Run [Vitest](https://vitest.dev) unit **and UI** tests inside real NativeScript
runtimes on Android and iOS.

If you encounter an issue, please log it at https://github.com/NativeScript/nativescript-cli/
Vitest stays on your machine as the orchestrator — configuration, CLI,
reporters, `--ui`, coverage, and editor integrations all work as usual — while
your specs execute on device/emulator inside the actual V8/JSC runtimes, with
full access to native APIs and the NativeScript UI layer.

### Troubleshooting
> Version 5 is a complete rewrite. Karma-based testing (v4 and below) is
> deprecated; see the [migration guide](./docs/migrating-from-karma.md).

If you see an error like this:
## Quick start

```bash
ns test init --framework vitest
ns test ios # or: ns test android
```
Error: connect ECONNREFUSED ::1:9876
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1195:16)

Or run Vitest directly (what editor extensions and CI use):

```bash
NS_PLATFORM=ios npx vitest run
```

## How it works

- A Vitest plugin installs a custom pool that forwards Vitest's standard
worker protocol over a WebSocket bridge (bound to `127.0.0.1`).
- The plugin launches your app via `ns run <platform> --no-hmr
--env.unitTesting`; the webpack helper (auto-discovered from this package)
swaps the bundle entry to your `test.ts` for test builds only.
- On device, a coordinator connects back to the host and executes specs:
- **Main-thread context (default):** specs run on the UI thread, so they can
create Views, navigate Frames, and use every NativeScript API.
- **Worker contexts (opt-in):** additional isolated NativeScript `Worker`
runtimes for parallel, non-UI specs.
- Results flow through Vitest's normal RPC, so every reporter works unchanged.

## Configuration

```ts
// vitest.config.mts
import { defineConfig } from 'vitest/config';
import { nativeScript } from '@nativescript/unit-test-runner';

export default defineConfig({
plugins: [
nativeScript({
platform: (process.env.NS_PLATFORM as 'android' | 'ios') || 'ios',
device: process.env.NS_DEVICE || undefined,
// workers: 2, // extra Worker runtimes for non-UI specs
// mainThread: false, // disable the UI-capable slot (workers only)
// port: 17878,
// launch: false, // attach to an app you run yourself
}),
],
});
```

Per-platform setups are possible with [Vitest projects](https://vitest.dev/guide/projects)
— give each project its own `nativeScript({ platform })` plugin instance.

## UI testing

```ts
import { describe, expect, it } from 'vitest';
import { Button } from '@nativescript/core';
import { mount, tap } from '@nativescript/unit-test-runner/testing';

describe('counter button', () => {
it('increments on tap', async () => {
let count = 0;
const { view } = await mount(() => {
const button = new Button();
button.text = 'Count';
button.on('tap', () => (count += 1));
return button;
});

await tap(view);
expect(count).toBe(1);
expect(view.isLayoutValid).toBe(true);
});
});
```

When using node 17 or higher, make sure your `karma.conf.js` contains a server hostname setting, for example:
`mount()` attaches the view to the host page scaffolded in your `test.ts`,
waits for `loaded` + a real layout pass, and auto-unmounts when the test
finishes. Also available from `./testing`: `tap`, `doubleTap`, `longPress`,
`enterText`, `returnPress`, `waitForLayout`, `waitUntil`, `nextRenderPass`.

UI specs require the main-thread context (the default). Files routed to
Worker contexts must not touch Views.

## Coverage

```bash
ns test ios --env.codeCoverage
# or: NS_PLATFORM=ios npx vitest run --coverage
```
// web server hostname (ensure this is present)
hostname: '127.0.0.1',

// web server port
port: 9876,
Use the `istanbul` provider — device runtimes do not expose V8 coverage:

```ts
test: {
coverage: { provider: 'istanbul', reporter: ['text', 'lcov'] },
},
```
See [here](https://github.com/NativeScript/nativescript-cli/commit/81cb9c37cdd4e24115be79b24b68dfbaf8cdcfd2) for changeset in CLI which adds that to all newly initialized unit test setups.

## Devices and networking

| Target | Transport |
| --- | --- |
| iOS simulator | host loopback (`127.0.0.1`) |
| Android emulator | `10.0.2.2` → host loopback |
| Physical Android | USB via automatic `adb reverse` |
| Physical iOS | pass a LAN-reachable `url` to the coordinator in `test.ts` |

The host server binds `127.0.0.1` by default. Test builds on Android may need
a scoped cleartext exception for `10.0.2.2`/`127.0.0.1`; on iOS,
`NSAllowsLocalNetworking`.

## Support matrix

| Feature | Status |
| --- | --- |
| `describe` / `it` / hooks / `expect` | ✅ |
| Reporters, `vitest --ui`, JUnit output | ✅ |
| Istanbul coverage | ✅ |
| UI testing (`mount`, gestures) | ✅ main-thread context |
| `vi.fn` / `vi.spyOn` / fake timers | 🚧 planned |
| Snapshots | 🚧 planned |
| Watch mode | 🚧 planned (one-shot `vitest run` today) |
| `vi.mock` module mocking | ❌ not supported (webpack static bundle) — prefer DI |

## Credits

The host↔device bridge design originates from
[`@cross-code/vitest-ns`](https://github.com/listepo/cross-code) by
[@listepo](https://github.com/listepo) (MIT). Thank you!

## License

Apache-2.0
2 changes: 0 additions & 2 deletions app/app-root.xml

This file was deleted.

81 changes: 0 additions & 81 deletions app/app.css

This file was deleted.

3 changes: 0 additions & 3 deletions app/app.ts

This file was deleted.

2 changes: 0 additions & 2 deletions app/bundle-app-root.xml

This file was deleted.

13 changes: 0 additions & 13 deletions app/bundle-app.ts

This file was deleted.

5 changes: 0 additions & 5 deletions app/bundle-main-page.ts

This file was deleted.

7 changes: 0 additions & 7 deletions app/bundle-main-page.xml

This file was deleted.

5 changes: 0 additions & 5 deletions app/main-page.ts

This file was deleted.

7 changes: 0 additions & 7 deletions app/main-page.xml

This file was deleted.

Loading