JavaScript testing with Jasmine presents an efficient and convenient way to cover your code with test early in development, allowing to greatly improve it in most cases. In this article, I will explore Behavior Driver Development (BDD) paradigm and show the basics of JavaScript testing with this framework.
There are some theoretical information and practical examples. I will also give you an instruction on how to set up your Visual Studio to run tests in this framework the same way as common .NET tests. This Jasmine testing tutorial will be useful for both beginners and developers who want to use it for their .NET projects.
You can always learn more about our .NET development expertise.
Contents
Written by:
Nikita Evdokimenko,
Junior .NET Developer
1. Introduction
Imagine writing .NET project with JavaScript. JQuery is the best JavaScript library for HTML client-side scripting, but even if you know what youโre doing with it, itโs a good idea to practice caution. Your implementation of JQuery may appear to work fine. However, it is almost guaranteed that the code has some hidden problems and the only way to uncover them is to start testing.
Testing your code during development is one of the best practices you can follow. However, testing on a high quality level may be quite hard: you need to retest your functionality after implementing changes to prevent regression, build and manually test features while developing. It takes a great deal of developerโs time and even then, some of these important issues might still left unresolved. Finally when you have something broken you cannot say what exactly is wrong at once. If this situation is familiar to you, then covering your code with tests is the solution. A great way to test your code is to use Jasmine testing tool.
So, writing tests for your project will:
- Prevent regression. You can run your test before committing your changes to make sure the old functionality works fine.
- Reduce manual testing. There are always operations that can be tested without manual testing (result of search, business calculations, data formatting)
- Check yourself. When you write one more test, you may find a case that will surely break your code logic before running tests.
- Make your code cleaner. To write tests easily, your code needs to be well separated, contain Inversion of Control (so you can mock it) and have a possibility to be configured. It is really terrible to cover hardcoded functionality.
- Satisfy business requirements. Sometimes customers require a certain percentage of test coverage.
In this article I will show your how to cover your JavaScript logic with Jasmine unit testing.
2. Jasmine review
Jasmine is a behavior-driven development framework for testing JavaScript code. It is free, has a simple syntax and provides a nice set of features. Furthermore, community surrounding this framework is large, so it is easy to find solutions for the problems you may face. Unit testing with this framework provides a fast and simple way to cover your codes with tests.
There is a great way to describe what BDD stands for, and also describe the syntax of Jasmine: โIn a BDD style test you describe your code and tell the test what it should be doing. Then you expect your code to do something.โ
A simple example of Jasmine JavaScript testing looks like this:
describe("math", function () {
it("should sum 1+1", function () {
expect(1 + 1).toEqual(2);
});
});
Think about โdescribeโ as an equivalent of โnamespaceโ that helps you organize your code. โItโ is your unit test. I will explain this in more detail later, after I show you how to set up your environment for Jasmine.
3. Set up the environment
You can download Jasmine at https://github.com/jasmine/jasmine/releases. Unzip it an you will see:
- Lib: contains source code for the framework
- Spec: contains code for your tests
- Src: contains source code for your application
- SpecRunner.html is a page that runs your tests. You can see references to your specs (tests) and source code for the application you want to test. So it is not necessary to copy your code to โsrcโ folder, you can just link your specs in this file.
This package also contains a little example that might be useful.
If you are using Visual Studio, just go to โTools -> NuGet Package Manager -> Package Manager Consoleโ. Type โInstall-Package JasmineTestโ.
Jasmine engine, examples, and SpecRunner will be added to your project. If you are using MVC, you can create a Controller to run your test.
public class JasmineController : Controller
{
public ViewResult Run()
{
return View("SpecRunner");
}
}
Also there is possibility to run Jasmine test as a common unit test in Visual Studio. Go to โTools -> Extentions and Updatesโ, click โOnlineโ and search for โChutzpahโ. Install โChutzpah Test Adapter for the Test Explorerโ.
Restart your Visual Studio and build the project. If you succeed, you will see all tests in Test Explorer.
Note! If you want to use Test Explorer to test your JavaScript, you need to add references to the source code only in your specs, as follows:
///
describe("App", function () {
describe("foo", function () {
it("should return bar", function () {
expect(App.foo()).toEqual("bar");
});
});
});
So we link our spec to search โAppโ in โapplication.jsโ that is one level up. โapplication.jsโ contains the following code:
var App = {
foo: function() {
return "bar123";
}
};
4. How to test with Jasmine JS testing
In the previous example we tested App.foo()
using toEqual
. There is a lot of other matchers:
.not.toEqual(expectedResult)
.toBeDefined()
.toBeUndefined()
.toBeNull()
.toBeTruthy()
.toBeFalsy()
.toBeLessThan(number)
.toBeGreaterThan(number)
.toContains(substring)
.toBeCloseTo(number, accuracy)
โ this one rounds up an argument inexpect(argument)
to the mentioned accuracy and compares it with a number..toMatch(/regexp/)
.toThrow()
โ catches exceptions and the test is passed if an exception was thrown. As an argument you can pass an error message you expect.
You can combine different matchers to write them one under another. The test will fail if any of them fail.
If you want to add your own matcher you can do it. Just add a new function to the spec you describing.
describe("foo", function () {
beforeEach(function () {
var matchers = {
toBeHaveFirstLetter: function () {
return {
compare: function (actual, expected) {
return { pass: actual[0] === expected };
}
}
}
}
jasmine.addMatchers(matchers);
});
it("should return bar", function () {
expect(App.foo()).toEqual("bar");
});
it('first letter b', function () {
expect(App.foo()).toBeHaveFirstLetter("b");
});
});
beforeEach
is called before running every spec. There is also afterEach
.
Same as every other testing framework, Jasmine has tools to mock functions and objects. It is called โspyโ. Letโs add to our App
object a new function doubleFoo
that calls foo
twice.
var App = {
foo: function() {
return "bar";
},
doubleFoo: function(){
return this.foo() + " " + this.foo();
}
};
And we create two test cases that use โspyโ.
describe("foo", function () {
it("called", function () {
spyOn(App, 'foo');
App.foo();
expect(App.foo).toHaveBeenCalled();
});
it("called 2 time", function () {
spyOn(App, 'foo');
App.doubleFoo();
expect(App.foo.calls.count()).toEqual(2);
The function spyOn
mocks the function in the mentioned object, so we can learn how many times it was called and with which arguments. As you see, creating spy on App.foo allows us to read the property calls
and get its count or inspect every call.
We can also get:
.toHaveBeenCalledWith(param)
.andCallThrough()
โ call the original function, not mock.
While creating spy we can change the return value or even the body of the function:
spyOn(object, โfunctionโ).andRetuns(โresโ)
spyOn(object, โfunctionโ).andCallFake(function(){ return โbbarโ})
jasmine.createSpyObj(โobj', [ โfunc1', โfunc2'])
โ it will create an object, where all functions are mocked.
You can also Jasmine mock window or other global object by using dependency injections.
5. Using Jasmine with AngularJS
In this section, I will show how to easily integrate Jasmine JS testing with AngularJS framework. First of all, you need to add references to โangular.jsโ, โangular-mocks.jsโ and your source code with your angular entities. Then you need to create an instance of angular.module and controller you want to test. Letโs review an example. We have this simple angular application:
var myApp = angular.module('myApp', []);
myApp.controller('myController', function ($scope) {
$scope.text = "qwe"
$scope.caps = function()
{
$scope.text = "QWE"
}
});
And here is our spec.
/// <reference path="../angular.js">
/// <reference path="../angular-mocks.js">
/// <reference path="../app/main.js">
describe('myApp', function () {
var scope, controller;
beforeEach(function () {
module('myApp');
});
describe('myController', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('myController', {
'$scope': scope
});
}));
it('have default text', function () {
expect(scope.text).toBe('qwe');
});
it('makes text to caps', function () {
scope.caps();
expect(scope.text).toBe('QWE');
});
});
});
We create an instance of angular.module
and variables to store the scope and controller before testing every controller. Inside the test of every controller we create the new scope and the controller we want. Use inject
to inject Angular services to beforeEach
function. Then we pass our scope to the controller. After that we can test our Angular controller as described before.
I would also like to show you how we can mock and test requests to server with Angular. We have a controller which calls our API to get users
by some group id.
myApp.controller('userController', function ($scope, $http) {
$scope.getUsers = function (id) {
return $http.get("/api/users/" + id).then(function (response) {
if (response.data.success) {
return response.data.data;
}
else {
return null;
}
})
};
})
Here is the description of the spec. I will explain it further.
describe('userController', function () {
var $httpBackend,
expectedUrl = '/api/users/123',
promise;
beforeEach(inject(function ($rootScope, $controller, _$httpBackend_) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
controller = $controller('userController', {
'$scope': scope
});
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
We create some variables to store the promise and $httpBackend
. Pay attention, that the scope and controller are described in myApp
description. $httpBackend
is a service to mock http requests. You can read more about it here: https://docs.angularjs.org/api/ngMock/service/$httpBackend. But now all you need to know is that it just allows us to mock requests. After each test we call $httpBackend.verifyNoOutstandingExpectation()
and $httpBackend.verifyNoOutstandingRequest()
to refresh $httpBackend
.
it('returns http requests successfully', function () {
var data = '{"success":"true", "data":"users"}';
$httpBackend.expectGET(expectedUrl).respond(200, data);
promise = scope.getUsers(123);
promise.then(function (res) {
expect(res).toBe('users');
});
$httpBackend.flush();
});
it('returns http requests with an error', function () {
$httpBackend.expectGET(expectedUrl).respond(500, 'Oh no!!');
promise = scope.getUsers(123);
promise.then(function (res) {
expect(res).toBeNull();
});
$h
We mock request to expectedUrl (โ/api/users/123โ)
to answer with success and return data. Our request returns the promise, so we wait until it finishes and check our result. Next test checks if our application returns null
if we get an error from the server. Note, that here we donโt test requests sending and getting data, here we perform unit test to check only our JavaScript code and how it responds to the data returned from the server.
6. Summary
As you can see, Jasmine unit test is a great example on how to cover your JavaScript logic with tests using BDD. As it is integrated with AngularJS it also can be used with other frameworks. So I find it to be a really great testing tool. I hope you found this Jasmine JS tutorial useful, and will incorporate methods, described here into your own workflow.
References
Check official documentation for more information https://github.com/jasmine/jasmine/wiki.
Angular documentation is full of examples how to test it with Jasmine https://docs.angularjs.org/api.
If you want to learn more about BDD see http://dannorth.net/introducing-bdd/.
Articles I was referring to:
http://bittersweetryan.github.io/jasmine-presentation/
http://inviqa.com/blog/2014/10/28/testing-javascript-get-started-with-jasmine
http://code.tutsplus.com/tutorials/testing-your-javascript-with-jasmine–net-21229
Read also: Unity IoC Tutorial