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.
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 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!