The application we will work on, is a Spring and Angular web project, which is built using Maven. In order to import Angular and the rest front end libraries and frameworks, we use Webjars, a technology that allows us to package the client-side web libraries into Jar files.
In the modern web applications, more and more business logic functions and calculations are being tranferred to the front end environment for speed and simplicity. This makes the need for Javascript testing even bigger. The main problem that appears in a Maven project is that we want those tests to be part of our continous integration environmnent and run on the Maven build. Here, we will show how to perform automated unit tests for our Angular functions and integrate them into the Maven lifecyrcle.
The full github repository can be found here: maven-karma-jasmine
Technologies Used:
- Java 8
- Spring MVC (5.0.2)
- Webjars
- Angular (1.6.6)
- Maven (3.5.2)
- Karma
- Jasmine
Implementation
Yarn
First of all we need to define the javascript dependencies used by our tests. We will use Yarn as the dependency management technology, so the first step is to download and set it up to our environment. The installation instructions can be found here: https://yarnpkg.com/en/docs/install. Right after that, we need to create a package.json file. We navigate to the root folder of our project and initialize yarn. After the creation, we can add the needed dependencies to our project. Here is the full yarn command list for this project:
- yarn init
- yarn add jasmine-core
- yarn add karma
- yarn add karma-chrome-launcher
- yarn add karma-jasmine
maven-dependency-plugin
Karma requires the list of all the files needed for the application to run correctly. In this list we have to setup the javascript files of our application, the testing javascript files we will create, and the javascript dependencies our application needs. In order to provide Karma with the Webjars dependencies, we need to unpack them as javascript files in the target folder. This is can be done using the maven-dependency-plugin:
pom.xml
<build> ... <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.0.2</version> <executions> <execution> <goals> <goal>unpack-dependencies</goal> </goals> <configuration> <includes>**/*.js</includes> </configuration> </execution> </executions> </plugin> ... </plugins> </build>
Karma configuration
The next step is to configure the karma test runner. This configuration will make it possible to run on the Maven lifecycle, so we will add to the default karma conf file name the ci (continuous integration) :
karma.conf.ci.js
// Karma Continuous Integration configuration module.exports = function(config) { config.set({ basePath: '../../../../', frameworks: ['jasmine'], files: [ 'target/dependency/META-INF/resources/webjars/angular/**/angular.min.js', 'target/dependency/META-INF/resources/webjars/angular-mocks/**/angular-mocks.js', 'src/main/webapp/js/*.js', 'src/main/webapp/js/**/*.js', 'src/test/webapp/js/*.js' ], exclude: ['**/karma.*.js'], preprocessors: {}, reporters: ['progress'], port: 9876, colors: true, logLevel: config.LOG_INFO, concurrency: Infinity, //The next properties are overridden by the development mode configuration browsers: ['ChromeHeadless'], autoWatch: false, singleRun: true }) };
frontend-maven-plugin
After configuring Karma we need somehow to run the node and yarn commands in order to create the testing environment and run the tests. We will use the frontend-maven-plugin for this, a maven plugin which allows us to install node and yarn locally and run the commands. We need three execution blocks inside the plugin. The first installs node and yarn, the second install the yarn dependencies into the node_modules folder and the third runs the karma tests using the continuous integration configuration. Note that this plugin required maven latest than 3.3.1.
pom.xml :
<build> ... <plugins> ... <plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <version>1.6</version> <executions> <execution> <id>install node and yarn</id> <goals> <goal>install-node-and-yarn</goal> </goals> <configuration> <nodeVersion>${node.version}</nodeVersion> <yarnVersion>${yarn.version}</yarnVersion> </configuration> </execution> <execution> <id>yarn install</id> <goals> <goal>yarn</goal> </goals> </execution> <execution> <id>js tests</id> <goals> <goal>karma</goal> </goals> <configuration> <karmaConfPath>src/test/webapp/js/karma.conf.ci.js</karmaConfPath> </configuration> </execution> </executions> </plugin> </plugins> </build>
Jasmine spec files
Now everything is configured properly and its time to write our test files. We use the Jasmine library for that. Alternatively we could use Mocha combined with Sinon and Chai, by using the same configuration and update the frameworks attribute in the karma.conf.ci.js file. In the Math.service.spec.js file we declare the Service’s module, inject the service, test the initialization and then we perform the unit tests.
Math.service.spec.js :
describe('MathService', function() { var MathService; beforeEach(function() { // Declare the angular module used module('services'); // Initialize the angular component tested (Service, Controller, etc) inject(function (_MathService_) { MathService = _MathService_; }); }); // Test the initialization describe('Angular component definition', function(){ it('MathService should be defined', function() { expect(MathService).toBeDefined(); }); }); describe('Test math functions', function(){ it('Test sum function', function() { expect(MathService.sum(2,3)).toBe(5); }); it('Test multiply function', function() { expect(MathService.multiply(2,3)).toBe(6); }); }); });
Run the tests
Finally it’s time to try the ci tests. In order to ensure that everything is working properly, we delete any node and node_modules folders that may have already been created and run mvn clean install. This will :
- Unpack all the dependencies needed in the target folder.
- Install node and yarn locally.
- Run yarn install in order to fetch and build the packages.
- Run karma start using the karma.conf.ci.js configuration to run the tests.
Finally, before pushing everything to the git repository, we will update the gitignore file with the node and node_modules folders that will be created by the yarn commands.
Karma test development mode
During the Jasmine test development, we need a fast way to try our tests. Using the current karma configuration, we must perform a maven build everytime we change something and want to test it. To solve this problem, we will use an extra karma configuration which will open a test browser (Chrome) and run the tests on each change we make. We use karma’s default configuration file name which is karma.conf.js, in which we load the ci configuration and override the browser, singleRun and autoWatch properties. By doing that we are able to run karma start command from the karma.conf.js folder.
karma.conf.js :
//Load base configuration var baseConfig = require('./karma.conf.ci.js'); module.exports = function(config) { baseConfig(config); // Override base configuration config.set({ browsers: ['Chrome'], singleRun : false, autoWatch : true }); };
Installation and Run
In order to run the tests during the maven lifecyrcle we just need to build the project.
- mvn clean install