Setting up SonarQube with Angular
Recently, I wanted to perform static code analysis on an Angular project. In my team, we want to analyze code quality, test coverage, code-smells, etc. Sonarqube was an easy choice for us since it provides all of the above with a nice UI.
You can also drill down and check the coverage vs tech-debt graph for each file in your project:
I had a community edition of Sonarqube running on a dedicated instance. However, integrating it with Angular was not really straightforward. I also found that a few of the tutorials on the internet were either obsolete or not working (due to various problems).
Hence I decided to document the steps that have worked for me.
I am currently working on Angular 11.2.2 (Though most of this should work with previous angular versions also).
Step 1: Figure out if all tests are running properly
To run Angular tests and generate coverage information, you can use the command:
ng test --watch=false --code-coverage
Additionally, if you want to speed up the process a bit and you don’t plan to debug the tests, you can use source-map=false
parameter.
Note: — watch=false
parameter used here will kill the Karma server once the tests are done running.
If you get any errors in this test run, you will not see the coverage info (in the terminal). Additionally, coverage info will also not be present in the coverage
folder inside src/
(this step will not happen if your tests are failing or throwing warnings that may break ng test
).
Pro-tip: you can choose to always generate coverage with ng test
command by modifying your angular.json file, and modifying the options object in the JSON document, like this:
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"codeCoverage": true, //This option
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": ["src/styles.scss"],
....
.... // Rest of the options document
}
Please fix all tests that are breaking. Additionally, you can choose to xdescribe
or xit
those tests.
Check for step 1: Your src/
folder should have a coverage folder inside and it should contain alcov.info
file (at some level* — not necessarily at the coverage/
folder level — depending on your karma config 🙂).
Step 2: Add SonarQube plugin to Karma.Conf
SonarQube will use the lcov.info
file to get information about the typescript/javascript code quality. However, to map a TS/JS file with its Unit-Test file (Spec file) and make it understand coverage details, you need to use SonarQube plugin with Karma.
Install the plugin using the command
npm install -D karma-sonarqube-reporter
And head over to karma.conf.js and make the following changes:
- import the plugin
require('karma-jasmine-html-reporter'),require('karma-coverage-istanbul-reporter'),require('@angular-devkit/build-angular/plugins/karma'),require('karma-sonarqube-reporter'), // This one
2. Add sonarqube to the array of reporters:
reporters:
[‘progress’, ‘kjhtml’, ‘sonarqube’]
3. Configure Sonarqube in Karma.conf like this:
sonarqubeReporter: {
basePath: 'src/app', // test files folder
filePattern: '**/*spec.ts', // test files glob pattern
encoding: 'utf-8', // test files encoding
outputFolder: 'reports', // report destination
legacyMode: false, // report for Sonarqube < 6.2 (disabled)
reportName: function (metadata) {
// report name callback, but accepts also a
// string (file name) to generate a single file
/**
* Report metadata array:
* - metadata[0] = browser name
* - metadata[1] = browser version
* - metadata[2] = plataform name
* - metadata[3] = plataform version
*/
return 'sonarqube_report.xml';
},
},
Check for Step 2: If you run ng test --code-coverage --watch=false
now, there is a folder reports/
generated at the project root with sonarqube_report.xml
file inside the folder.
This part I figured out, thanks to: https://morioh.com/p/6971eef66101
Step 3: Update your .gitignore
Add these to your .gitignore since you never want them on version-tracking.
.scannerwork/*
reports/*
Step 4: Install “sonar-scanner”
npm install -D sonarqube-scanner
This is the wrapper over SonarQube Scanner .jar. However, you will not need Java to run this (therefore, your docker images are also lighter).
Sonar-Scanner works exactly to the JAR also. The NPM package also has very good docs (https://www.npmjs.com/package/sonarqube-scanner). You can refer to either docs.
Step 5: Create a sonar-project.properties file in your project root
sonar.host.url=SONAR-QUBE-URLsonar.projectKey=Project_Key # Must be unique (Primary key)sonar.projectName=PROJECT_NAME # name that will be displayed on the dashboardsonar.projectVersion=1.0sonar.sourceEncoding=UTF-8sonar.sources=srcsonar.exclusions=**/node_modules/**sonar.tests=src # Where to pickup test files fromsonar.test.inclusions=**/*.spec.ts # only collect these for test-coveragesonar.javascript.lcov.reportPaths=src/coverage/PROJECT_NAME/lcov.infosonar.testExecutionReportPaths=reports/sonarqube_report.xml
Additionally, add the sonar.login
and sonar.password
if you have password-protected your SonarQube instance (Only when running locally!).
Note: this is for testing purposes only. When running on production, please generate a token . This way, when running the code, you do not need a sonar.password
property, and you can pass in the token to sonar.login
option.
Step 6: Run the SonarQube Scanner:
Run
npm run sonar-scanner
at the root of your project (or simply sonar-scanner
if it is in your path
). This will fire up the process and you should see a new entry for your project in your sonarqube instance. You can also choose run a local version of SonarQube on Docker.
Step 6a (Optional): Debugging if sonar-scanner breaks midway.
If Sonar-Scanner breaks mid-way due to memory issues, the process usually shows a detailed stack trace and the options you need to set.
Usually, it is:
sonar.javascript.node.maxspace=4096 (Or Whatever value you choose)
However, sometimes it can also be vm-options
. The error message is usually very clear. Follow that and add them to your .properties
file.
If it fails even after this, try increasing the value a bit more.
On my MacBook Pro, Killing docker
, closing down a few tabs in the browser and doing a watch
over the process really helped me figure out what was going on (99% of the cases, it is memory issues — on mine, the memory for the process kept increasing but my laptop memory was full).
Step 7: Putting it all together
- Update your
package.json
with two new tasks:
{
....
"test:headless": "ng test --watch=false --code-coverage --source-map=false",
"sonar-scanner": "sonar-scanner"
}
2. If running sonar-scanner is a separate step in your CI pipeline (after ng-test), do not forget to mark required artifacts for the next step 🚨. For my Gitlab pipelines, I have done it like so:
runUnitTestsMaster:
stage: test
image: ${UI_TEST_IMAGE}
before_script:
- npm config set cache /tmp/gitlab/cache/npm --global
script:
- ng run
artifacts:
paths:
- src/coverage
- reports
expire_in: 1 day
only:
- master
- RELEASE
- HOTFIX
3. You can run sonarqube using the command:
npm run sonar-scanner -- -Dsonar.login=${SONARQUBE_REPORTER_TOKEN}
The token you use here is generated from SonarQube administration (It is not a User-name password pair but a single token). You can store it in your CI/CD secrets or choose to fetch it from any secret manager this way.
Additionally, you can choose to fail the pipeline if the code does not pass your quality standards using the parameter -Dsonar.qualitygate.wait=true
.
Step 8 (Optional): Use SonarQube as build-breaker:
If you are on SonarQube 8.1 or later versions, SonarQube provides a Quality-Gate
by default — https://docs.sonarqube.org/latest/user-guide/quality-gates/. You can also define your own set of rules to check if your project is “release” ready.
You can use sonar.qualitygate.wait
parameter to break your pipeline if it fails quality checks.
You should now have your SonarQube check-up and running with your pipeline.