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.
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.
ns create
You now have a project that should build and run, you can check with ns run android
.
We are going to need some code to test so let's add a service.
Create a file demo.service.ts
in src/app/services
folder
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();
}
ns test init
Choose the framework jasmine
This installs the various frameworks etc.
If you execute ns test android
at this point you will get compile errors, this is because we have not configured our tsconfig.json
to 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.unitTesting
is true).
Now we can execute the sample test.
Launch the android emulator
Execute ns test android
As 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.js
add the following inside the chainWebpack
we 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 android
again you will see the tests all completing.
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 tests
folder named test-main.ts
Add 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.js
for the new file
Add tests/test-main.ts
to the start of the filePatterns
array.
Add our test for the doDemo method on our Search component.
Add a new file angular-example.spec.ts
to the tests
folder 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')
})
})
beforeEach
we are setting up TestBed, telling it about our component and the providers required for it to function.Running ns test android
will 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.
For reference, the sample project is available here: https://github.com/jcassidyav/nativescript-testing-demo
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...
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.
karma.conf.js
file add 'junit'
to the list of reporterse.g.
reporters: ['progress', 'junit'],
junitReporter: {
outputDir: '../TestResults',
outputFile: 'TESTS_RESULTS.xml'
},
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")
Jason Cassidy has been developing software since the 90s and helped found Avaeon Solutions in 2001 based in Galway, Ireland.