I am trying to get better at testing, on .NET Core I was getting better, but angular projects in SonarQube had always %0 test coverage. even angular has a built-in testing environment, I have never used it before.
I use CI-CD everywhere and I want my angular tests to run before the Docker image created.
How Can I configure Angular for Tests
I can simply run ng test to test my angular app, if you are using angular-cli generate (ng g) you already have tests for your components services etc… but if you are like me they are probably broken. if you import modules and use them in your components or inject services to services or components it will break your tests.
first, start fixing them
run ng test and you will get error messages
before test starts you can see injected items are missing or if your test creates instance manually parameters missing. if you have generic items you can also see those generic items are not reflected on the tests. add them fix them till all clear out and karma starts.
Specially Directives can cause a headache to test
How can I test Directives
We run directives with components and tests should be the same. We need to create a component, add the directive and test the directive in test component
first, create a test component by ng g c test-tools/test (you don’t have to change anything on this is just a container for directives.
let’s find the directive test file (.directive.spec.ts) or create a new one to test a directive without a test
import { TestBed, ComponentFixture, async } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; import { By } from '@angular/platform-browser'; import { TestComponent } from '../test-tools/test/test.component'; import { ToggleFullscreenDirective } from './toggle-fullscreen.directive'; describe('ToggleFullscreenDirective', () => { let component: TestComponent; let fixture: ComponentFixture<TestComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CommonModule], providers: [], declarations: [ToggleFullscreenDirective, TestComponent], }).overrideComponent(TestComponent, { set: { template: '<div class="mytestclass" appToggleFullscreen>NoAuth</div>' } }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should onclick works', () => { const directiveEl = fixture.debugElement.query(By.directive(ToggleFullscreenDirective)); expect(directiveEl).not.toBeNull(); const directiveInstance = directiveEl.injector.get(ToggleFullscreenDirective); expect(directiveInstance.onClick()).toEqual(true); }); });
On line 11, Create Test Module by importing every module your directive need. Add services to providers and add component and the directive to declarations. Override test component and add the directive to the component by overrideComponent (I want to use this component for all directives to test not just this directive keeping the template dynamic will allow me to do that). compileComponents returns a Promise, async waits till it compiles the component
should “onclick” works query the fixture to find the directive checks it is injected after it calls onClick function and checks the return value.
Next, read errors on Karma and inject services, or modules if they are missed on tests it may take some times (mine took 3 hours) but you can get the green light in the end.
Open package.json file and add below inside scripts block
“test:ci”: “ng test –browsers ChromeHeadlessCI –code-coverage true –watch false”
your package.json should be similar to this:
{ "name": "client-app", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "test:ci": "ng test --browsers ChromeHeadlessCI --code-coverage true --watch false" }, "private": true, "dependencies": { .........
As I am planning to run this with my CI, CD pipeline and in a Docker container, I need to run those test headless –browsers ChromeHeadlessCI handle that part.
As we are running this to collect code coverage for SonarQube –code-coverage true self-explanatory.
I want to run the test once and collect the result I am not after a file watcher on this –-watch false
Next stop karma.conf.js you can find it in src folder of your project open it and Add customLaunchers after the plugins file should be like below
module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], customLaunchers: { ChromeHeadlessCI: { base: 'ChromeHeadless', flags: ['--no-sandbox'] } }, client: { clearContext: false // leave Jasmine Spec Runner output visible in browser },
before adding SonarQube in to the picture open a PowerShell window in your Project Folder and Call
npm run test:ci
you should also see coverage folder in your project’s root folder.
as the test is running fine we can start the last step getting them into SonarQube
first Create sonar-project.properties file in the root folder of your angular project,
I used SonarCloud and installed an instance of SonarQube this file works fine with both (if you are running your SonarQube Instance, you don’t need sonar.organization)
sonar.host.url=https://sonarcloud.io sonar.organization=[put_your_organization_for_sonarcloud_ignore_otherwise] sonar.login=[put_GeneratedTokens_here (https://docs.sonarqube.org/latest/user-guide/user-token/)] sonar.projectKey=[put_your_project_key] sonar.projectName=[put_your_project_name] sonar.projectVersion=1.0 sonar.sourceEncoding=UTF-8 sonar.sources=src sonar.exclusions=**/node_modules/**,**/assets/**,**/*.spec.ts sonar.tests=src sonar.test.inclusions=**/*.spec.ts sonar.ts.tslintconfigpath=tslint.json sonar.typescript.lcov.reportPaths=coverage/lcov.info
this file by itself useless we need the sonar-scanner npm package to use this. let’s install it now.
run npm install sonar-scanner –save-dev
npm install sonar-scanner –save-dev in the root folder of your angular project. this will install sonar scanner
now re-open package.json file and add “sonar”: “sonar-scanner”, just after the test:ci line
it should be like
npm install sonar-scanner –save-dev
{ "name": "client-app", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "sonar": "sonar-scanner", "test:ci": "ng test --browsers ChromeHeadlessCI --code-coverage true --watch false" }, "private": true, "dependencies": { .........
now you can run
npm run test:ci
npm run sonar
the first step will run the tests and the second line will send the result to SonarQube
Bonus Content: Docker image to run them all (dockerfile below does it all)
Repository: https://github.com/mmercan/sentinel/tree/master/Sentinel.UI.HealthMonitoring
FROM node:11.0 as test ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y vim && apt-get -y install sudo RUN apt-get install -y openjdk-8-jdk RUN java -version RUN apt-get install -y --no-install-recommends chromium ENV CHROME_BIN=chromium EXPOSE 4300 USER node RUN mkdir /home/node/.npm-global ENV PATH=/home/node/.npm-global/bin:$PATH ENV NPM_CONFIG_PREFIX=/home/node/.npm-global RUN npm install -g @angular/cli WORKDIR /home/node/health COPY . . USER root RUN sudo npm install RUN npm run test:ci; exit 0 RUN npm run sonar
Next Step is adding more tests covering more or maybe failing the image build if Quality gate Fails.