Unit Testing with Angular 12
We will run through an example adding tests to a NativeScript for Angular app which execute on the device of our choice.
We all know that testing is important and thanks to the changes in NativeSript to support Angular 12 this is easier than ever with NativeScript for Angular projects.
Sample project to demonstrate
We will run through an example adding tests to the sample SideDrawer Angular template. When we are done we will have tests which execute on the device of our choice.
Create the project
ns create- Enter a Name e.g. nstestingdemo
- Choose angular flavour
- Choose SideDrawer template
You now have a project that should build and run, you can check with ns run android.
Add a service that we can test
We are going to need some code to test so let's add a service.
-
Create a file
demo.service.tsinsrc/app/servicesfolder -
Add the content
import { Injectable } from '@angular/core' @Injectable({ providedIn: 'root', }) export class DemoService { doSomething(): string { return 'correct message' } }This is a simple service that we can inject into our controllers with a single method that returns a value.
-
Import the service into the search controller (
src/app/search/search.component.ts)import {DemoService} from '../services/demo.service'; -
Now inject the service into the search controller
constructor(private demo: DemoService) { // Use the component constructor to inject providers. } -
Add a method to the search controller which uses this service
doDemo(): string { return this.demo.doSomething(); }
Add Testing to our project
-
ns test init -
Choose the framework jasmine
This installs the various frameworks etc.
If you execute
ns test androidat this point you will get compile errors, this is because we have not configured ourtsconfig.jsonto compile the sample tests. -
Configure the tsconfig.json for testing
Add a new file
tsconfig.spec.json( alongside tsconfig.json ) with the following contents:{ "extends": "./tsconfig.json", "include": ["./src/tests/test-main.ts", "./src/tests/*.spec.ts", "./src/tests/**/*.spec.ts", "**/*.d.ts"] }Here we extend the exiting configuration, but add in our test spec files.
-
Rename example.ts to example.spec.ts ( to conform to what we have entered in the previous step).
-
Configure webpack to use this new tsconfig.spec.json when we are running tests:
In the webpack.config.js add
if (env.unitTesting == true) { webpack.chainWebpack((config) => { config.plugin('AngularWebpackPlugin').tap((args) => { args[0].tsconfig = './tsconfig.spec.json' return args }) }) }Here we are setting the tsconfig file to our specific test one when running tests ( in other words when
env.unitTestingis true).
Run the tests
Now we can execute the sample test.
-
Launch the android emulator
-
Execute
ns test androidAs you watch the simulator you will see that the tests run, and then the app exits. It would be nice to see the app after it has executed so that we can look at the detail etc.
In
webpack.config.jsadd the following inside thechainWebpackwe added above.const webpack = require('@nativescript/webpack') const { merge } = require('webpack-merge') module.exports = (env) => { if (env.unitTesting == true) { webpack.chainWebpack((config) => { config.plugin('AngularWebpackPlugin').tap((args) => { args[0].tsconfig = './tsconfig.spec.json' return args }) // keep the test runner open config.plugin('DefinePlugin').tap((args) => { args[0] = merge(args[0], { __TEST_RUNNER_STAY_OPEN__: true, }) return args }) }) } }This sets a flag to keep the app running after the test run has completed.
Executing
ns test androidagain you will see the tests all completing.
Setup Angular TestBed
The Service we created above is injected by Angular into our components so we need to use Angular TestBed to test our components and have those services available to our tests.
-
Add setup code
Add a new file in the
testsfolder namedtest-main.tsAdd the content
import '@nativescript/core/globals' import '@nativescript/angular/polyfills' import '@nativescript/zone-js/dist/pre-zone-polyfills' import 'zone.js' import '@nativescript/zone-js' import 'zone.js/testing' import { TestBed } from '@angular/core/testing' import { NativeScriptTestingModule } from '@nativescript/angular/testing' import { platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing' TestBed.initTestEnvironment([NativeScriptTestingModule], platformBrowserDynamicTesting()) -
Update the
karma.config.jsfor the new fileAdd
tests/test-main.tsto the start of thefilePatternsarray. -
Add our test for the doDemo method on our Search component.
Add a new file
angular-example.spec.tsto thetestsfolder with the following contents:import { TestBed } from '@angular/core/testing' import { SearchComponent } from '../app/search/search.component' import { DemoService } from '../app/services/demo.service' import { Page } from '@nativescript/core' describe('search.component', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [SearchComponent], providers: [DemoService, Page], }).compileComponents() }) it('should fail', async () => { const fixture = TestBed.createComponent(SearchComponent) const component = fixture.componentInstance as SearchComponent expect(component.doDemo()).toEqual('angular-component-testing') }) it('should succeed', async () => { const fixture = TestBed.createComponent(SearchComponent) const component = fixture.componentInstance as SearchComponent expect(component.doDemo()).toEqual('correct message') }) })- In the
beforeEachwe are setting up TestBed, telling it about our component and the providers required for it to function. - We have two tests, one will fail as it tests for the incorrect message.
Running
ns test androidwill now show our extra tests where some fail.As this is a live watch testing mode we can go ahead and change the "should fail" test to test for the correct value, save the file and see the tests now pass.
- In the
Sample Project
For reference, the sample project is available here: https://github.com/jcassidyav/nativescript-testing-demo
Running Tests in a CI Environment
Here's a couple extra tips if you have a CI environment setup for your builds which you'd like testing to be a part of...
Configure a reporter
Some CI Environments allow you to publish your tests results as part of the build, for example in Azure you can use the Publish Test Task to publish the results.
For this you will need to configure a reporter so output the results of the tests.
- In the
karma.conf.jsfile add'junit'to the list of reporters - Configure the reporter as desired
e.g.
reporters: ['progress', 'junit'],
junitReporter: {
outputDir: '../TestResults',
outputFile: 'TESTS_RESULTS.xml'
},
Launch the emulator
A CI environment introduces challenges around launching/killing the instances of an emulator on which to run your tests.
-
If your CI is setup to run only one test run at a time, it is a good idea to kill the emulator at the start and end of each run.
-
Passing -no-window to emulator.exe results in a headless emulator
-
You can use adb to poll the emulator until it is booted before trying to run your tests.
e.g. On windows Powershell:
do { $outVar=cmd /c "C:\Android\android-sdk\platform-tools\adb.exe shell getprop sys.boot_completed" '2>&1' $outVar=$outVar.Split([Environment]::NewLine) | Select -First 1 echo $outVar Start-Sleep -Seconds 10 } Until ($outVar -eq "1")
Contact Avaeon for more help
Jason Cassidy has been developing software since the 90s and helped found Avaeon Solutions in 2001 based in Galway, Ireland.