Back to Blog Home
← all posts

NativeScript-Vue unit testing with Vitest

December 8, 2023 — by Juan de Dios Martínez

NativeScript (NS) is a framework for building native applications using JS/TS. If we combine NS with Vue we can build native high-performance applications and also, due to the incredible architecture of NS, NS-Vue and Vue, we can use Vue dependencies as if it were a frontend application. In this case we focus on unit testing with Vitest, a testing framework that is gaining more traction every day, created by the Vue team but made to be able to test almost all the frameworks that we use together with its older brother Vite.

Setup

The first thing to do is install the necessary dependencies for the tests.

npm i -D @types/[email protected] @vitejs/plugin-vue @vue/test-utils vitest jsdom

Now that we have the dependencies, we start with the code. Before starting with the configuration of vitest we will create two files that we will use to configure the tests.

To start, for this sample we will create the test folder in the root directory of the project in which we will add our tests and files for the general configuration. Inside this folder we create the file NSMockViews.ts which will contain an array of the views offered by NativeScript. If you have any views of a plugin you can add it to this list. We will use this file in the vitest configuration.

// test/NSMockViews.ts
export const NSMockViews = [
  'AbsoluteLayout',
  'ActionBar',
  'ActionItem',
  'ActivityIndicator',
  'Button',
  'ContentView',
  'DatePicker',
  'DockLayout',
  'FlexboxLayout',
  'FormattedString',
  'Frame',
  'GridLayout',
  'HtmlView',
  'Image',
  'Label',
  'ListPicker',
  'ListView',
  'NavigationButton',
  'Page',
  'Placeholder',
  'Progress',
  'ProxyViewContainer',
  'RootLayout',
  'ScrollView',
  'SearchBar',
  'SegmentedBar',
  'SegmentedBarItem',
  'Slider',
  'Span',
  'StackLayout',
  'Switch',
  'TabView',
  'TabViewItem',
  'TextField',
  'TextView',
  'TimePicker',
  'WebView',
  'WrapLayout',
  'Prop',
  'Template',
];

Now we will create the setup.ts file inside the test folder. In this file we mock nativescript-vue with @vue/test-utils and we will also mock @nativescript/core.

// test/setup.ts
import Vue from '@vue/test-utils';
import { vi } from 'vitest';

/* MOCK Vue & Nativescript */
vi.mock('nativescript-vue', () => Vue);
vi.mock('@nativescript/core', async () => {
  return {
    default: () => ({}),
    TouchManager: {},
  };
});

So that TypeScript doesn't complain in our tests, we'll add the test folder to tsconfig.json in the include section.

// tsconfig.json
"include": [
    "src",
    "types",
+   "test"
],

With all the base configuration for our tests, we create the vitest.config.ts file that vitest will use to launch our tests.

// vitest.config.ts
import Vue from '@vitejs/plugin-vue';
import path from 'path';
import { defineConfig } from 'vitest/config';
import { NSMockViews } from './test/NSMockViews';

export default defineConfig({
  plugins: [
    Vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) =>
            NSMockViews.map((nsView) => nsView.toLowerCase()).includes(
              tag.toLowerCase()
            ),
        },
      },
    }),
  ],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '~': path.resolve(__dirname, './src'),
    },
    extensions: ['.mjs', '.js', '.ts', '.json', '.vue'],
  },
  test: {
    globals: true,
    name: 'jsdom',
    environment: 'jsdom',
    setupFiles: ['test/setup.ts'],
  },
});

We briefly explain what we have configured here:

  • We use NSMockViews to tell vue that NativeScript components are customElements.
  • We added the default NativeScript-Vue aliases so that it resolves the components of our application.
  • We indicate the base configuration of the tests.

Build test and run

We now have everything configured. Let's continue creating a basic component that will show an icon to do the tests later on this component.

// src/components/Icon.vue

<script lang="ts" setup>
const props = defineProps({ icon: String });
</script>

<template>
  <Label :text="props.icon" class="m-icon-round text-center"></Label>
</template>

Let's take action, let's do our first test. We create the file test/components/icon.test.ts and add some basic tests, such as checking that the component receives a prop and the NS Label view has the text that we are passing to the component.

// test/components/icon.test.ts
import Icon from '@/components/Icon.vue';
import { mount, VueWrapper } from '@vue/test-utils';
import { beforeEach, describe, expect, test } from 'vitest';

let wrapper: VueWrapper<any, any>;

beforeEach(async () => {
  wrapper = mount(Icon, {
    props: {
      icon: 'my-icon',
    },
  });
});

describe('Icon Component', async () => {
  test('mount component', async () => {
    expect(wrapper.html()).toBeTruthy();
  });

  test('should have icon as text', async () => {
    expect(wrapper.attributes('text')).toContain('my-icon');
  });

  test('should update icon ', async () => {
    expect(wrapper.attributes('text')).toContain('my-icon');
    await wrapper.setProps({ icon: 'updated-icon' });
    expect(wrapper.attributes('text')).toContain('updated-icon');
  });
});

We already have our first test. Let's add the script to launch vitest in our package.json file.

"scripts": {
  "test": "vitest"
},

We are ready to launch the tests, now you just have to launch the tests and you should have 3 tests passing.

npm run test

And that's it! We only had to make a couple of mocks and define the NativeScript views as customElemts.

Here I leave a repository with a base project ready to launch the tests with vitest: https://github.com/vallemar/nativescript-vue-vitest

Happy Testing!