Andreas Rozek
[ Imprint ]   [ Data Privacy Statement ]   [ Contact ]       [ deutsche Fassung ]   english version

Jasmine

Jasmine [1] is a well-made library for behaviour-driven testing of Javascript functions. Tests created for Jasmine can run both in the backend (under Node.js [2]) and in the frontend (i.e. in the browser).

On this page you will learn

  • how to check asynchronously thrown exceptions and
  • achieve multiple test runs in the browser as well as
  • how to create a customised "reporter".

A guide explains how to use the pages in this website, their behaviour may be adjusted in the settings.

Checking asynchronously thrown Exceptions

Jasmine can check (synchronously) thrown exceptions and wait for results of asynchronous functions - but checking asynchronously thrown exceptions is not directly supported. Fortunately, little effort is required to address this shortcoming.

Checking synchronously thrown exceptions is simple:

describe('test of synchronously thrown exceptions', function() {
it('synchronously thrown exceptions are simple', async function() {
function erroneousFunction () {
throw new Error('function failed')
}

expect(erroneousFunction).toThrow()
expect(erroneousFunction).toThrow(new Error('function failed'))
expect(erroneousFunction).toThrowError('function failed')
expect(erroneousFunction).toThrowError(Error,'function failed')
})
})

Waiting for results of asynchronous functions is not difficult either:

describe('test of asynchronously delivered results', async function() {
it('asynchronously delivered function results are simple', async function() {
function asyncResult () {
return new Promise((resolve,reject) => {
setTimeout(() => resolve('delivered'), 10)
})
}

expect(await asyncResult()).toBe('delivered')
})
})

However, the (actually straight-forward) combination of both procedures fails:

describe('test of asynchronously thrown exceptions', async function() {
it('combining both approaches will fail', async function() {
function erroneousFunction () {
return new Promise((resolve,reject) => {
setTimeout(() => reject(new Error('function failed')), 10)
})
}

expect(await erroneousFunction).toThrow()
expect(await erroneousFunction).toThrow(new Error('function failed'))
expect(await erroneousFunction).toThrowError('function failed')
expect(await erroneousFunction).toThrowError(Error,'function failed')
})
})

A remedy is provided by one or two small auxiliary functions that catch thrown exceptions and deliver them to the test as "normal" return values:

describe('test of asynchronously thrown exceptions, done right', async function() {
it('a slightly different approach will succeed', async function() {
function erroneousFunction () {
return new Promise((resolve,reject) => {
setTimeout(() => reject(new Error('function failed')), 10)
})
}

function thrownMessageOf (FunctionToTest) {
return FunctionToTest().then(
() => undefined,
(Signal) => Signal.message || Signal.toString()
)
}

function thrownExceptionOf (FunctionToTest) {
return FunctionToTest().then(
() => undefined,
(Signal) => Signal
)
}

expect(await thrownMessageOf(erroneousFunction)).toBeDefined()
expect(await thrownMessageOf(erroneousFunction)).toBe('function failed')
expect(await thrownExceptionOf(erroneousFunction)).toBeDefined()
expect(await thrownExceptionOf(erroneousFunction)).toEqual(new Error('function failed'))
})
})

You should therefore define the following two functions in advance:

function thrownMessageOf (FunctionToTest) {
return FunctionToTest().then(
() => undefined,
(Signal) => Signal.message || Signal.toString()
)
}

function thrownExceptionOf (FunctionToTest) {
return FunctionToTest().then(
() => undefined,
(Signal) => Signal
)
}

and formulate the actual test as follows:

expect(await thrownMessageOf(async throwing Function)).toBeDefined()
expect(await thrownMessageOf(async throwing Function)).toBe(your error message)

expect(await thrownExceptionOf(async throwing Function)).toBeDefined()
expect(await thrownExceptionOf(async throwing Function)).toEqual(your thrown object)

Multiple Test Runs in the Browser

Jasmine can run and log tests directly in the browser. Unfortunately, the tests are executed exactly once, namely immediately after visiting the associated web page. With just a little effort, however, you can get Jasmine to run the tests specifically on demand.

The trick is to "reset" the "environment" that Jasmine sets up after loading the required CSS and JavaScript files (especially, before creating the actual tests) and to explicitly start the tests later.

For this you need the following two small functions:

function Jasmine_reset () {
window['jasmine'] = jasmineRequire.core(jasmineRequire);

var jasmineEnvironment = jasmine.getEnv(); // Jasmine environment
var jasmineInterface = jasmineRequire.interface(jasmine, jasmineEnvironment);
function extend (Target, Source) {
for (var Key in Source) { Target[Key] = Source[Key] };
return Target;
};
extend(window, jasmineInterface);
};

function Jasmine_runTests () {
window['jasmine'].getEnv().execute();
};

which you can best define immediately after loading the script files belonging to Jasmine.

Before creating the actual tests, first reset the Jasmine test environment:

Jasmine_reset();

After creating the tests, you can now start them conveniently using

Jasmine_runTests();

The sequence

Jasmine_reset();
define your tests
Jasmine_runTests();

can be repeated at will - both with the same and with different tests.

Application-specific "Reporter"

The form of logging tests performed in the browser is actually determined by Jasmine. Fortunately, however, this default can be circumvented and the output adapted to the requirements of a browser-based development environment, for example.

The trick is to enter a special "reporter" in the Jasmine test environment, which then takes over the logging during the tests.

If you are using the Jasmine_reset function shown earlier, you can simply extend it accordingly:

function Jasmine_reset () {
window['jasmine'] = jasmineRequire.core(jasmineRequire);

var jasmineEnvironment = jasmine.getEnv(); // Jasmine environment
var jasmineInterface = jasmineRequire.interface(jasmine, jasmineEnvironment);
function extend (Target, Source) {
for (var Key in Source) { Target[Key] = Source[Key] };
return Target;
};
extend(window, jasmineInterface);

var customReporter = {
your custom reporter, see below
};
jasmineEnvironment.addReporter(customReporter);
};

A "reporter" is nothing more than a simple JavaScript object with a few specific methods:

var customReporter = {
jasmineStarted: function (SuiteInfo) { ... },
suiteStarted: function (Result) { ... },
specStarted: function (Result) { ... },
specDone: function (Result) { ... },
suiteDone: function (Result) { ... },
jasmineDone: function (Result) { ... }
}

Writing your own "reporter" is not particularly difficult - you can find more detailed instructions (and a simple example) in the documentation for Jasmine.

You do not need to implement all the methods mentioned above. It is best to start with the example in the documentation and adapt the output step by step to your needs.

If you define three small functions that control the actual output

function   clear () { insert your own implementation }
function print () { insert your own implementation }
function println () { insert your own implementation }

you may start with the following implementation for a trivial "reporter":

var customReporter = {
jasmineStarted: function (SuiteInfo) {
clear();
println('Running suite with ' + SuiteInfo.totalSpecsDefined + ' Specs');
},
suiteStarted: function (Result) {
println('"' + Result.description + '":');
},
specStarted: function (Result) {
print(' - "' + Result.description + '":');
},
specDone: function (Result) {
println(' ' + Result.status);
for (var i = 0, l = Result.failedExpectations.length; i < l; i++) {
var Failure = Result.failedExpectations[i];
println(' Failure: ' + Failure.message);
println(Failure.stack);
};
},
jasmineDone: function (Result) {
println('all tests completed');
for (var i = 0, l = Result.failedExpectations.length; i < l; i++) {
var Failure = Result.failedExpectations[i];
println('Global ' + Failure.message);
println(Failure.stack);
};
},
};
jasmineEnvironment.addReporter(customReporter);

References

[1] Pivotal Labs
Jasmine - Behavior-Driven JavaScript
(see https://jasmine.github.io/)
MIT License
Jasmine is a well-made library for "behaviour-driven testing" of Javascript functions. Tests created for Jasmine can run both in the backend (under Node.js) and in the frontend (i.e. in the browser).
[2] OpenJS Foundation
Node.js
(see https://nodejs.org/)
several Licenses
from Wikipedia: "Node.js is an open-source, cross-platform, back-end JavaScript runtime environment that ... executes JavaScript code outside a web browser"

This web page uses the following third-party libraries, assets or StackOverflow answers:

The author would like to thank the developers and authors of the above-mentioned contributions for their effort and willingness to make their works available to the general public.