Photo by Robert Anasch on Unsplash

Jasmine is a behavior-driven development framework for testing JavaScript code by describing acceptance criteria in terms of scenarios. Once system fulfills all acceptance criteria it is behaving correctly. Applying BDD approach to writing tests improves application quality and maintainability, and serves as a live documentation.

We started describing the acceptance criteria in terms of scenarios, which took the following form:
Given some initial context (the givens),
When an event occurs,
then ensure some outcomes.

https://dannorth.net/introducing-bdd/

BDD’s approach to testing provides a template for capturing story’s acceptance criteria as a set of sentences, like in the real conversation. At the same time it provides an answer on

  • where to start when writing tests
  • what to test and what not to
  • how much to test in one go
  • what to call a tests
  • how to understand why test fails

For more details check Dan North’s blog at https://dannorth.net/introducing-bdd/

Table of Content

Core concepts

NOTE: Core Jasmine functionalities are described using pseudo code.

In terms of BDD, Jasmine provides describe() function which serves as a container for a group of related specifications tests (story, feature, scenario). In addition to that, Jasmine provides it() function that’s used as a container for a specification behaviour validation (acceptance criteria, specification, spec). Desired specification implementation behaviour is verified via expectations.

  • Used together, describe, it and expectations helps express story and acceptance criteria as a complete sentence / conversation
describe('an order')
  it('sums the prices of its line items')
    expect(order.total == 100.00)
  • Matcher implements a boolean comparison between the actual value and the expected value. It is responsible for reporting to Jasmine if the expectation is true or false. Jasmine will then pass or fail the spec.
describe('an order')
  it('sums the prices of its line items')
    expect(order.total).not.toBe(0.00)
    expect(order.total).toBe(100.00)
  • Hooks in Jasmine can be used to provide shared setup and/or tear-down before/after each specification in describe block is called
describe('an order')
  beforeAll('run once before all specs are called')

  beforeEach('run before each spec is called')

  it('sums the prices of its line items')
    expect(order.total == 100.00)

  afterAll('run tear-down once after all of the specs are called')
  
  afterEach('run tear-down after each of the specs is called.')
  • Spies provides a test double function. A spy can stub any function and track calls to it and all its arguments
describe('an order printer')
  
  spyOn('printerObject', 'printOrderMethod')

  it('prints the order')
    printerObject.printOrderMethod()
    expect(printerObject.printOrderMethod() to have been called)
  • Jasmine also provides a mock Clock object that can be used to test time dependent code and mock dates.
    NOTE: It is important to uninstall the clock after the test to restore the original functionality!
describe('clock')
  
  beforeEach()
    jasmine.clock.install()

  afterEach()
    jasmine.clock().uninstall();

  it('sets the clock to tomorrow')
    jasmine.clock().mockDate(tomorrow)
    jasmine.clock.tick(1 day)
    expect (currentDate === tomorrow)
  • Testing code that requires asynchronous operations is supported by Jasmine. There are three ways to indicate a function is asynchronous: by taking an optional callback parameter, by returning a promise, or by using the async keyword in environments that support it.

Basic Setup with Angular

Angular project created with Angular CLI has preconfigured infrastructure needed for testing the application. That includes Jasmine test framework and Karma test runner. Protractor is also included.

Example usage of Jasmine BDD testing framework is shown on following business scenario:
GIVEN: An order
WHEN: Order total amount is requested
THEN: Sum the price of it’s line items and return the value

Project setup

  • Create a new Angular project
$ ng new jasmine-demo
  • create a new Order class to test
$ ng generate class order/order
CREATE src/app/order/order.spec.ts (150 bytes)
CREATE src/app/order/order.ts (23 bytes)

Create a test

  • under order.spec.ts file add new test to verify acceptance criteria
describe('Order', () => {

  it ('sums the prices of its line items', () => {
    const givenOrder = new Order();
    const givenOrderAmount = givenOrder.getTotal()
    const expectedOrderAmount = 100.00;

    expect(givenOrderAmount).toEqual(expectedOrderAmount);
  })
});
  • now implement Order’s getTotal() method
export class Order {
  getTotal(): number {
    return 100.00;
  }
}
  • run test ng test
$ ng test
...
 Chrome 88.0.4324.192 (Mac OS): Executed 1 of 1 SUCCESS (0.075 secs / 0.012 secs)
 TOTAL: 1 SUCCESS

Bonus

Multiple Jasmine run configurations

In enterprise projects it is often needed to execute only a subset of tests in a project. Jasmine supports partial execution of tests at runtime by providing an option to execute:

  • only one spec or
  • only those spec whom file names match a certain glob pattern
jasmine spec/appSpec.js                        # only one spec
jasmine "**/model/**/critical/**/*Spec.js"     # match glob pattern

It is also possible to create a Jasmine configuration file where tests needed to be executed are described as:

  • Spec directory path relative to the current working dir when jasmine is executed.
  • Array of filepaths (and globs) relative to spec_dir to include and exclude

Example Jasmine configuration

Providing custom Jasmine configuration through JSON file is possible by:

  • creating a new custom-jasmine-config.json file with following content
{
  "spec_dir": "location/to/spec/dir/to/execute",

  "spec_files": [
    "**/*[sS]pec.js",  # include those
    "!**/*nospec.js"   # exclude those
  ]
}
  • adding a new script in package.json
 "scripts": {
    ...
    "custom-test-run": "jasmine --config=./custom-jasmine-config.json",
    ...
  },
  • execute provided script via npm
$ npm run custom-test-run
   jasmine-demo@0.0.0 custom-test-run /angular/jasmine-demo
   jasmine --config=./custom-jasmine-config.json 
 Started
 No specs found
 Finished in 0 seconds

Additional configuration details are available at: https://jasmine.github.io/setup/nodejs.html

Conclusion

Having out of the box integration with Jasmine BDD testing framework, Angular provides everything needed to start building high quality testable applications. Applying BDD approach to software development assures that story’s acceptance criteria are working as expected and are bringing business value.

Sources