Unit testing JavaScript with Karma in 2021. Starting point 🌻
Karma was born in the era of AngularJS. This was a significant improvement of existed test tooling on the Web and was one of the reasons for the spread of TDD in the JavaScript community. Karma still the best test runner for web JavaScript after almost 10 years thanks to the architecture and principles it’s based on.
Let's set up it for a test project and try to understand how it works. The first step is to install it via NPM
npm install --save-dev karma
After successful installation, we would have Karma binary inside the local node_modules folder. Now let’s create the Karma configuration file for our project using the init
command
npx karma init
Here we could select the testing framework from the list of popular frameworks like “Jasmine”, “Mocha” etc. In our example, we would use “Jasmine” so just press [Enter] to move to the next question.
Do you want to use Require.js ?
no
Require.js was popular 10 years ago for management dependencies when we didn’t have Webpack. Now it’s mostly rudiment so here we select [no]
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
We would use Chrome as a default browser for our tests because this is one of the popular browsers nowadays and works on most of operating systems
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> src/**/*.test.js
> src/**/*.js
Here we should tell Karma where we keep our source and test files. In our example, we would use src/**/*.test.js
patter for test files which are widespread now, and src/**/*.js
for source files
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>
Leaves it empty
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes
Karma can be run in two modes “single run” or “watch”. Single run means that when tests are finished karma would stop and to rerun our tests we should do run tests manually again. In the “watch” mode Karma would automatically rerun our tests when we change either the source or test files. This pretty handy feature saves our time, so we select [yes] here
Config file generated at "/<project>/karma.conf.js".
We have finished the configuration and Karma says that the result is a karma.conf.js
file. But this is not all. Karma also has installed necessary dependencies based on our choices and updated package.json
+ "karma-chrome-launcher": "^3.1.0",
+ "karma-jasmine": "^4.0.1",
In our case these were
karma-chrome-launcher
the plugin which helps run tests in Chromekarma-jasmine
an adapter for jasmine
Karma has a plugin architecture. Most of the language and frameworks specific functionality provided by plugins. Karma itself as a runner provides only functionality to run arbitrary JavaScript in a browser via Karma HTTP server. Also, Karma gives you the possibility to process your test and source files before they would be sent to the browser. So roughly say Karma is a mediator between your test/source files, frameworks, browsers, and operating system. Thus, this architecture allows karma to do only one thing but do it well and be open for extension without changing the Karma source itself. I highly encourage you to watch this video and look at the Karma source code itself as an example of a good product that stood the test of time.
Let’s move back to our test project and finish the Karma setup. Because we decided to use Jasmine as a test framework we should install it too.
npm install jasmine
karma-jasmine only a Karma adapter for Jasmine itself so this is why we should install Jasmine alongside karma-jasmine.
Now we are ready to write our first test. Let’s create a file src/foo.test.js
and write a test case
describe('My first Karma test', function() {
it('should be true', function() {
expect(true).toEqual(true);
});
});
Before start Karma make sure that you have installed Chrome which we select as our default browser for tests. Now we are ready to run our tests
npx karma start
This command would start the Karma server, open the Chrome browser, and run our tests. The result of tests would be printed in the terminal
30 01 2021 14:59:09.329:WARN [karma]: No captured browser, open http://localhost:9876/
30 01 2021 14:59:09.353:INFO [karma-server]: Karma v6.0.3 server started at http://localhost:9876/
30 01 2021 14:59:09.353:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
30 01 2021 14:59:09.358:INFO [launcher]: Starting browser Chrome
30 01 2021 14:59:10.799:INFO [Chrome 88.0.4324.96 (Mac OS 11.1.0)]: Connected on socket kj6wu3FYg7XPKEKCAAAB with id 15222593
Chrome 88.0.4324.96 (Mac OS 11.1.0): Executed 1 of 1 SUCCESS (0.002 secs / 0.002 secs)
TOTAL: 1 SUCCESS
Here you can notice several interesting points
- Karma starts the server
- Open
http://localhost:9876
in Chrome - Run tests and print the result
When tests finished Karma would not stop and wait for changes to rerun tests automatically. Let’s change our test to see how it works
it('should be true', function() {
expect(true).toEqual(false);
});
And now look at the result
30 01 2021 15:02:37.443:INFO [filelist]: Changed file "/playground/src/foo.test.js".
Chrome 88.0.4324.96 (Mac OS 11.1.0) My first Karma test should be true FAILED
Error: Expected true to equal false.
at <Jasmine>
at UserContext.<anonymous> (src/foo.test.js:3:18)
at <Jasmine>
Chrome 88.0.4324.96 (Mac OS 11.1.0): Executed 1 of 1 (1 FAILED) (6.226 secs / 0.001 secs)
TOTAL: 1 FAILED, 0 SUCCESS
You can see that Karma automatically rerun the test and told us that now we have 1 failed test. Also, the result contains useful information about the error. The error message contains
- Name of a test suite. In our case
My first Karma test
- Name of a test case —
test should be true
- Expectation/assert error message —
Expected true to equal false
- Exception stack trace
Because Karma runs tests inside a browser you can use the browser’s developer tools to debug your tests. For this purpose, you can set a breakpoint in a test and open developer tools in Chrome. Also, you can open http://localhost:9876/debug
page where tests would be run in the main window and don’t produce the result in the terminal. If you want to run your tests in another browser you can easily do it without stopping tests and changing configuration by open http://localhost:9876/
in an interesting browser. It would attach the browser to the Karma server and run tests on it too. This approach also allows you to run tests on any device and any browser just by opening Karma server URL.
Ok. Let’s fix our test by reverting to the previous change. To stop tests press [Ctrl-C] in the terminal where Karma is running. Now let’s change the Karma configuration file so that test runs without “watch” by default. To do this open karma.conf.js and change singleRun
from false
on true
singleRun: true,
Now if we run tests again by npx karma start
you can see Karma stops after tests finished. To run tests in watch mode we should pass --no-single-run
parameter
npx karma start --no-single-run
When karma running in watch mode you can rerun tests without changing source and test files by run
command. To do this we should open a new terminal window without terminating karma start
process and run
npx karma run
This command mostly useful for the integration of different tools with Karma.
Now let’s talk about source files. In the example above we have added the test file that contains all logic inside itself. Let’s create the file src/foo.js
and write function multiply
function multiply(x, y) {
return x * y;
}
now let’s change add test inside src/foo.test.js
so that it would check the result of function multiply
it('should multiply 2 on 2', function() {
expect(multiply(2, 2)).toEqual(4);
});
If you don’t stop Karma server you can see that it automatically detect new file and test
Chrome 88.0.4324.96 (Mac OS 11.1.0): Executed 2 of 2 SUCCESS (0.006 secs / 0 secs)
TOTAL: 2 SUCCESS
Now we have two tests. You can see that function multiply
available in the test file this is because all files are loaded and executed in the same context (on the same page). We had not restricted visibility of our function multiply
thus it’s available globally, and we can use it in our test.
Here I finish the article. This is a basic setup of Karma and starting point for advanced configuration. I recommend starting from the basic setup and when you are sure that it works correctly move towards advanced configuration which usually includes preprocessing step for web projects in our days.