© Panos Matsinopoulos 2020
P. MatsinopoulosPractical Test Automationhttps://doi.org/10.1007/978-1-4842-6141-5_1

1. Introduction to Jasmine

Panos Matsinopoulos1 
(1)
KERATEA, Greece
 

In this chapter, you are going to learn Test-Driven Development (TDD) using Jasmine.

TDD is a very popular development methodology. You can find lots of articles online about it and pictures like these (Figure 1-1).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig1_HTML.jpg
Figure 1-1

TDD Flow

On the other hand, Jasmine is a very popular testing framework for JavaScript code.

You are going to learn how to write your testing code and how to integrate it into a runner HTML page.

You will be able to see the test runner generating results as in Figure 1-2
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig2_HTML.jpg
Figure 1-2

Spec Runner Indicative Results

and matching this information against the Jasmine code (Figure 1-3).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig3_HTML.jpg
Figure 1-3

Test Results and Dummy Code

Finally, you will learn how to run Jasmine inside a JS Bin (Figure 1-4).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig4_HTML.jpg
Figure 1-4

Jasmine Through a JS Bin

Learning Goals

  1. 1.

    Learn how to download Jasmine.

     
  2. 2.

    Learn how to set up your local folders to work with Jasmine.

     
  3. 3.

    Learn how to set up your spec runner HTML page.

     
  4. 4.

    Learn how to write specs for your JavaScript code.

     
  5. 5.

    Learn how to interpret the results that you see being output at the spec runner page.

     
  6. 6.

    Learn about the describe function.

     
  7. 7.

    Learn about the it function.

     
  8. 8.

    Learn about the expect function.

     
  9. 9.

    Learn about Test-Driven Development (TDD).

     
  10. 10.

    Learn how to transfer all your requirements to corresponding tests.

     
  11. 11.

    Learn how you can safely do refactorings of your code, assuming that you have specs running successfully and you have full test coverage of your code under test.

     
  12. 12.

    Learn about the most important Jasmine matchers.

     
  13. 13.

    Learn about setup and teardown spec phases.

     
  14. 14.

    Learn how to try Jasmine on the JS Bin platform.

     
  15. 15.

    Learn how you can nest a describe block within another.

     
  16. 16.

    Learn how you can disable a suite of specs.

     
  17. 17.

    Learn how you can set a spec as pending implementation.

     

Introduction

Testing is the art of writing code that tests other code. When you run your tests and they all pass, then you feel confident that the code under test is doing the work right.

You are going to have an introduction to testing, using the JavaScript testing library called Jasmine.

Download

The GitHub page https://github.com/jasmine/jasmine/releases has all the releases of Jasmine. You are going to download the latest one (Figure 1-5).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig5_HTML.jpg
Figure 1-5

Download the Latest Jasmine

When you have the zip file downloaded, unzip it. This will create the folder jasmine-standalone-3.5.0 (or whatever version is the latest one when you did the download). Inside that folder, there is a subfolder lib/jasmine-3.5.0 which has the Jasmine code that you will need for your project.

A New JavaScript Project

You will now start a new JavaScript project. This will be a very simple JavaScript function that would take as input a string and return back an encrypted version of the string. The encryption will be very simple:
  • All characters will be replaced with their next one. So “a” will be replaced by “b”, “b” will be replaced by “c”, and so on.

  • If the input character is uppercase, then the output will be uppercase too.

  • Also, only characters from the Latin alphabet will be allowed. Otherwise, an exception will be thrown.

Let’s call that project String Encryption. Create a folder string-encryption in your working folder:
$ mkdir string-encryption
Then, cd to that folder. And then create the subfolder assets/javascripts/jasmine-standalone-3.5.0 inside the string-encryption folder:
$ cd string-encryption
$ mkdir -p assets/javascripts/jasmine-standalone-3.5.0
And then, create the subfolder assets/stylesheets/jasmine-standalone-3.5.0 inside the string-encryption folder. Do the same for assets/images/jasmine-standalone-3.5.0:
$ cd string-encryption
$ mkdir -p assets/stylesheets/jasmine-standalone-3.5.0
$ mkdir -p assets/images/jasmine-standalone-3.5.0

Then copy all the JavaScript *.js files from the lib/jasmine-3.5.0 download folder and put them inside the folder assets/javascripts/jasmine-standalone-3.5.0. Copy the CSS file lib/jasmine-3.5.0/jasmin.css to assets/stylesheets/jasmine-standalone-3.5.0. And, finally, copy the lib/jasmine-3.5.0/jasmine_favicon.png file inside assets/images/jasmine-standalone-3.5.0.

So you need to have something like Figure 1-6 in your working folder.
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig6_HTML.jpg
Figure 1-6

Local folder setup for Jasmine

Setting Up Jasmine

Basically, Jasmine will give you an environment to run your JavaScript tests. This is an HTML page that
  • Will reference Jasmine

  • Will reference your JavaScript files

  • Will reference your JavaScript tests

  • When loaded, will run your tests and display the results

This page is called a spec runner – although it could be called a test runner too. You need to understand that I, many times, call the tests specs instead, from the word specifications, because they specify the desired behavior of the code that I am developing.

The Jasmine convention for the name of the HTML page that will host and run your tests is SpecRunner.html.

Having said that, in the root folder of your project, create the HTML page SpecRunner.html with the following content (Listing 1-1).
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Jasmine Spec Runner v3.5.0</title>
    <link rel="shortcut icon" type="image/png" href="assets/images/jasmine-standalone-3.5.0/jasmine_favicon.png">
    <link rel="stylesheet" href="assets/stylesheets/jasmine-standalone-3.5.0/jasmine.css">
    <script src="assets/javascripts/jasmine-standalone-3.5.0/jasmine.js"></script>
    <script src="assets/javascripts/jasmine-standalone-3.5.0/jasmine-html.js"></script>
    <script src="assets/javascripts/jasmine-standalone-3.5.0/boot.js"></script>
    <!-- include source files here... -->
    <!-- ... -->
    <!-- include spec/test files here... -->
    <!-- ... -->
</head>
<body>
</body>
</html>
Listing 1-1

Bare-Bones SpecRunner.html Page Content

This is basically an empty body HTML page, and it works completely with JavaScript.

In Figure 1-7, I point out the important parts of this HTML page.
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig7_HTML.jpg
Figure 1-7

Important Parts of the SpecRunner.html

As you can see in the preceding picture (Figure 1-7), the SpecRunner.html source code initially references the Jasmine library files, and then it has two placeholders:
  1. 1.

    For the JavaScript code under test: In other words, you need to include the JavaScript source file that has the code that you want to run tests for.

     
  2. 2.

    For the JavaScript spec (a.k.a. test) files themselves.

     
If you open the SpecRunner.html page on your browser, you will see the following (Figure 1-8).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig8_HTML.jpg
Figure 1-8

SpecRunner Page Displayed – No Specs

You can see the No specs found message telling you that the spec runner didn’t find any specs to run. This is expected, of course, since you have not written any specs.

Write Your First Dummy Spec

You are going to write your first dummy spec , just to make sure that your spec runner can find specs and run them.

Let’s change the content of your SpecRunner.html to refer to your spec list file.

So instead of
<!-- include spec/test files here... -->
<!-- ... -->
you have
<!-- include spec/test files here... -->
<script src="assets/javascripts/my-application-specs.js"></script>
And then, let’s create the file assets/javascripts/my-application-specs.js with the following content (Listing 1-2).
describe("Dummy Spec List", function(){
    it("true is equal to true", function(){
        expect(true).toEqual(true);
    });
});
Listing 1-2

Initial Content of the my-application-specs.js File

If you save the preceding file and reload the page on your browser, you will see the following (Figure 1-9).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig9_HTML.jpg
Figure 1-9

Spec Runner Results – One Dummy Spec

Great! You have successfully run one spec. This spec didn’t test your application JavaScript code; it was only testing equality of true to itself. It was really a proof that the spec runner was successfully installed.

Until you start writing real tests, let’s see the anatomy of this dummy test and compare it to the output we see on the SpecRunner (Figure 1-10).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig10_HTML.jpg
Figure 1-10

Anatomy of the Dummy Spec

describe

With the describe function , you start your list of specs. Also, you give a name to this list of specs. In the preceding example, the list has the name Dummy Spec List. The name of the spec list will be printed at the top of the spec runner results. Then you give a function that will call each one of your specs.

it

The specs themselves are called to the it function . A describe may be calling it many times. Each call is a different spec execution. In the example, you only have one.

The it function takes as first argument the name of the spec. This will be printed, nested, below the name of the list the spec belongs to. Then, the it function takes as argument a function which is the actual spec code. This will be executed, and if everything goes well, the spec run will be considered a pass. Otherwise, it will be considered a failure.

expect

The expect function is a way to express to Jasmine your expectations. If the expectation is not met, then you have a test failure.

Let’s change the
expect(true).toEqual(true)
to
expect(true).toEqual(false)
Save and reload the page on your browser. You will see the following (Figure 1-11).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig11_HTML.jpg
Figure 1-11

Expectation Not Met – Test Failure

Cool! That was expected, because true is never equal to false.

Start Your Application Implementation: Test-Driven Development (TDD)

Now, you are sure that your setup works ok. But you have not yet done any real work on your project. This is what you will start now.

You remember that you wanted to develop a JavaScript function that would encrypt a string. Shall you just try to write this function?

No! You will follow the Test-Driven Development approach. This means that you will first write what you expect this function to do and then you will try to make it work.

Let’s write the following inside our assets/javascripts/my-application-specs.js (Listing 1-3).
describe("Encrypt String", function(){
    it("returns the next character", function(){
        expect(encrypt('a')).toEqual('b');
    });
});
Listing 1-3

First Real Expectation

It’s a very simple spec. It says that if you call encrypt('a'), then it should return b. With this expectation, you clearly say
  1. 1.

    That your function name will be encrypt

     
  2. 2.

    That it will be taking one string parameter

     
  3. 3.

    That when called with 'a' as argument, it will return the string 'b'

     
If you save that and load the SpecRunner.html page on your browser, you will get the following (Figure 1-12).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig12_HTML.jpg
Figure 1-12

Encrypt Is Not Defined Error

That was expected. The encrypt function is not defined yet. You need to define this function in your project.

Remember that your SpecRunner.html has a placeholder for you to put JavaScript reference to your project JavaScript files:
    <!-- include source files here... -->
    <!-- ... -->
Let’s create the file assets/javascripts/encrypt.js and put the following content inside:
function encrypt(inputString) {
}
And also, update the SpecRunner.html to reference your assets/javascripts/encrypt.js file:
    <!-- include source files here... -->
    <script src="assets/javascripts/encrypt.js"></script>
Now, everything is ready. The encrypt() function is defined, and your SpecRunner.html will locate it. Let’s reload SpecRunner.html (Figure 1-13).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig13_HTML.jpg
Figure 1-13

New Error: Expected Undefined to Equal ‘b’

Ok. You got rid of the error encrypt is not defined. You have a new error though: expected undefined to equal 'b'. It is clear that the expectation that you have specified inside the spec is not met. The encrypt('a') returns undefined, and this is not equal to b.

That is reasonable. If you look at the current implementation of your encrypt() function , you will see that it is empty.

So let’s try to write some proper implementation. Write the following inside the assets/javascripts/encrypt.js file:
function encrypt(inputString) {
    return 'b';
}
This function returns 'b'. Does it satisfy the specification expectation? Let’s load the SpecRunner.html page again (Figure 1-14).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig14_HTML.jpg
Figure 1-14

The First Spec Is Passing

Nice! The first spec that you wrote is passing successfully. Did you finish your function implementation? Is it correct? A Quality Assurance engineer reviews your spec code
describe("Encrypt String", function(){
    it("returns the next character", function(){
        expect(encrypt('a')).toEqual('b');
    });
});
and easily comes with the verdict. The spec does not make sure that your function works as expected. Let’s review the function requirements:
  • All characters should be replaced with their next one. So “a” should be replaced by “b”, “b” should be replaced by “c”, and so on.

  • If the input character is uppercase, then the output will be uppercase too.

  • If the input character is “z”, then the output should be “a” – similarly for the “Z”.

  • Only characters from the Latin alphabet will be allowed. Otherwise, an exception will be thrown.

These requirements are not expressed inside your spec file. Hence, you cannot be sure that the function that you have implemented works as required.

Let’s try to add some more expectations (Listing 1-4).
describe("Encrypt String", function(){
    it("returns the next character", function(){
        var expectations = {
            'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e', 'e': 'f',
            'f': 'g', 'g': 'h', 'h': 'i', 'i': 'j', 'j': 'k',
            'k': 'l', 'l': 'm', 'm': 'n', 'n': 'o', 'o': 'p',
            'p': 'q', 'q': 'r', 'r': 's', 's': 't', 't': 'u',
            'u': 'v', 'v': 'w', 'w': 'x', 'x': 'y', 'y': 'z',
            'z': 'a',
            'A': 'B', 'B': 'C', 'C': 'D', 'D': 'E', 'E': 'F',
            'F': 'G', 'G': 'H', 'H': 'I', 'I': 'J', 'J': 'K',
            'K': 'L', 'L': 'M', 'M': 'N', 'N': 'O', 'O': 'P',
            'P': 'Q', 'Q': 'R', 'R': 'S', 'S': 'T', 'T': 'U',
            'U': 'V', 'V': 'W', 'W': 'X', 'X': 'Y', 'Y': 'Z',
            'Z': 'A'
        };
        for (var property in expectations) {
            if (expectations.hasOwnProperty(property)) {
                var charToEncrypt = property;
                var expectedEncryptedChar = expectations[property];
                expect(encrypt(charToEncrypt)).toEqual(expectedEncryptedChar);
            }
        }
    });
});
Listing 1-4

More Expectations

You have amended your spec to run the encrypt() method call and the corresponding expect(...) method call for each one of the characters of the Latin alphabet. Basically, using a loop (with the for JavaScript statement), you are telling that you
  • expect(encrypt('a')).toEqual('b');

  • expect(encrypt('b')).toEqual('c');

  • expect(encrypt('c')).toEqual('d');

  • And so on

Nice. Save all these and load the SpecRunner.html page again. What you will see is something like the following (Figure 1-15).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig15_HTML.jpg
Figure 1-15

Expectations Fail

It seems that your function implementation is far behind from being ready. Almost all the expectations returned an error. Only the letter 'a' has been successfully converted to 'b'.

So you need to go back to the implementation of your function and revisit the code. You need to make it work so that all expectations are met.

Let’s change the content of the file assets/javascripts/encrypt.js with the following (Listing 1-5).
function encrypt(inputString) {
    var mapping = {
        'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e', 'e': 'f',
        'f': 'g', 'g': 'h', 'h': 'i', 'i': 'j', 'j': 'k',
        'k': 'l', 'l': 'm', 'm': 'n', 'n': 'o', 'o': 'p',
        'p': 'q', 'q': 'r', 'r': 's', 's': 't', 't': 'u',
        'u': 'v', 'v': 'w', 'w': 'x', 'x': 'y', 'y': 'z',
        'z': 'a',
        'A': 'B', 'B': 'C', 'C': 'D', 'D': 'E', 'E': 'F',
        'F': 'G', 'G': 'H', 'H': 'I', 'I': 'J', 'J': 'K',
        'K': 'L', 'L': 'M', 'M': 'N', 'N': 'O', 'O': 'P',
        'P': 'Q', 'Q': 'R', 'R': 'S', 'S': 'T', 'T': 'U',
        'U': 'V', 'V': 'W', 'W': 'X', 'X': 'Y', 'Y': 'Z',
        'Z': 'A'
    };
    return mapping[inputString];
}
Listing 1-5

Better Implementation of the encrypt Function

If you save the preceding file and reload the page SpecRunner.html, you will see that the spec runs successfully (Figure 1-16).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig16_HTML.jpg
Figure 1-16

Spec Runs Successfully

Great!

More Specs

Your spec specifies the behavior of your function when given a string of one character. You need to add some examples to make sure that it works for multicharacter strings too.

Let’s add one more expectation:
it("when input string is 'television' it returns 'ufmfwjtjpn'", function(){
  expect(encrypt('television')).toEqual('ufmfwjtjpo');
});
Note

The preceding expectation needs to be added after the existing it and before the closing }); of the describe.

Save and then load the page SpecRunner.html . What you will see is the following (Figure 1-17).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig17_HTML.jpg
Figure 1-17

Encrypting a Word Is Failing

You have two specs and one failure. The encryption of a word has failed. You can see the message
Expected undefined to equal 'ufmfwjtjpo'

Hence, you now have to visit, again, the code of the encrypt() function and make it work for words too.

Let’s change the implementation of encrypt() to be like the following (Listing 1-6).
function encrypt(inputString) {
    var mapping = {
        'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e', 'e': 'f',
        'f': 'g', 'g': 'h', 'h': 'i', 'i': 'j', 'j': 'k',
        'k': 'l', 'l': 'm', 'm': 'n', 'n': 'o', 'o': 'p',
        'p': 'q', 'q': 'r', 'r': 's', 's': 't', 't': 'u',
        'u': 'v', 'v': 'w', 'w': 'x', 'x': 'y', 'y': 'z',
        'z': 'a',
        'A': 'B', 'B': 'C', 'C': 'D', 'D': 'E', 'E': 'F',
        'F': 'G', 'G': 'H', 'H': 'I', 'I': 'J', 'J': 'K',
        'K': 'L', 'L': 'M', 'M': 'N', 'N': 'O', 'O': 'P',
        'P': 'Q', 'Q': 'R', 'R': 'S', 'S': 'T', 'T': 'U',
        'U': 'V', 'V': 'W', 'W': 'X', 'X': 'Y', 'Y': 'Z',
        'Z': 'A'
    };
    var result = "";
    for (var i = 0; i < inputString.length; i++) {
        result = result + mapping[inputString[i]];
    }
    return result;
}
Listing 1-6

Encrypting Words

As you can read here, the encrypt now parses each one of the characters of the inputString, encrypts it, and appends the encrypted char to the result which is finally returned.

If you save this implementation and reload the SpecRunner.html on your browser, you will see the following (Figure 1-18).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig18_HTML.jpg
Figure 1-18

Two Specs Successful – Words Being Converted

Perfect! Now your encrypt() method works both for single characters and for words.

But you only have one spec of word encryption. Maybe, you would like to add some more specs to make you feel more confident. Actually, what would make you feel more confident would have been a spec like the following (Listing 1-7).
it("can encrypt a word", function(){
    expect(encrypt('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')).
       toEqual('bcdefghijklmnopqrstuvwxyzaBCDEFGHIJKLMNOPQRSTUVWXYZA');
});
Listing 1-7

Spec for Encrypting Words with All Characters of the Latin Alphabet

So, if you replace your last spec (that one with the 'television' word) with this, you would be more confident that the encrypt() function can convert words with the letters of the Latin alphabet.

Save this spec, load the SpecRunner.html on the browser, and you will see the following (Figure 1-19).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig19_HTML.jpg
Figure 1-19

Spec Results for Words with All Chars

Nice! You are confident that your encrypt() function can encrypt both single-char strings and long words. But did you finish? Did you satisfy all the requirements? Let’s revisit them:
  1. 1.

    All characters should be replaced with their next one. So “a” should be replaced by “b”, “b” should be replaced by “c”, and so on.

     
  2. 2.

    If the input character is uppercase, then the output will be uppercase too.

     
  3. 3.

    If the input character is “z”, then the output should be “a” – similarly for the “Z”.

     
  4. 4.

    Only characters from the Latin alphabet will be allowed. Otherwise, an exception will be thrown.

     
It’s the last requirement that is not specified in any of the specs. Let’s try to define the specification for that (Listing 1-8).
it("throws an exception if there is any character that does not belong to the latin alphabet", function() {
    expect(function(){
        encrypt("This includes the blank character that does not belong to latin alphabet")
    }).toThrowError(ArgumentError, "non-latin alphabet character encountered");
});
Listing 1-8

Spec for the Last Requirement

The Jasmine pattern
expect(function() {
 ... call your function under test here ...
}).toThrowError(...);
is used to tell that an exception is expected to be thrown. Note that this will not work:
expect(
 ... call your function under test here ...
).toThrowError(...);
The function under test needs to be called from within an anonymous function. Hence, add the new spec inside your assets/javascripts/my-application-spec.js. This file then needs to be like the following (Listing 1-9).
describe("Encrypt String", function(){
    it("returns the next character", function(){
        var expectations = {
            'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e', 'e': 'f',
            'f': 'g', 'g': 'h', 'h': 'i', 'i': 'j', 'j': 'k',
            'k': 'l', 'l': 'm', 'm': 'n', 'n': 'o', 'o': 'p',
            'p': 'q', 'q': 'r', 'r': 's', 's': 't', 't': 'u',
            'u': 'v', 'v': 'w', 'w': 'x', 'x': 'y', 'y': 'z',
            'z': 'a',
            'A': 'B', 'B': 'C', 'C': 'D', 'D': 'E', 'E': 'F',
            'F': 'G', 'G': 'H', 'H': 'I', 'I': 'J', 'J': 'K',
            'K': 'L', 'L': 'M', 'M': 'N', 'N': 'O', 'O': 'P',
            'P': 'Q', 'Q': 'R', 'R': 'S', 'S': 'T', 'T': 'U',
            'U': 'V', 'V': 'W', 'W': 'X', 'X': 'Y', 'Y': 'Z',
            'Z': 'A'
        };
        for (var property in expectations) {
            if (expectations.hasOwnProperty(property)) {
                var charToEncrypt = property;
                var expectedEncryptedChar = expectations[property];
                expect(encrypt(charToEncrypt)).toEqual(expectedEncryptedChar);
            }
        }
    });
    it("can encrypt a word", function(){
        expect(encrypt('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')).
        toEqual('bcdefghijklmnopqrstuvwxyzaBCDEFGHIJKLMNOPQRSTUVWXYZA');
    });
    it("throws an exception if there is any character that does not belong to the latin alphabet", function() {
        expect(function(){
            encrypt("This includes the blank character that does not belong to latin alphabet")
        }).toThrowError(ArgumentError, "non-latin alphabet character encountered");
    });
});
Listing 1-9

Full Expectations Code

If you save and load the SpecRunner.html again, you will see the following (Figure 1-20).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig20_HTML.jpg
Figure 1-20

ReferenceError Is Thrown

The error that you see in the SpecRunner results is a runtime error and not an expectation error. It is a JavaScript error that is telling you that there is something wrong with the ArgumentError reference. This reference is not defined in your code, and that’s why this error is raised.

Let’s define this reference. It will be an object deriving from Error. In Listing 1-10, you can see the definition of ArgumentError.
function ArgumentError(message) {
    this.name = 'ArgumentError';
    this.message = message || 'Argument Error';
    this.stack = (new Error()).stack;
}
ArgumentError.prototype = Object.create(Error.prototype);
ArgumentError.prototype.constructor = ArgumentError;
Listing 1-10

ArgumentError Definition

Save this inside your encrypt.js file, above the definition of the encrypt() function, and then reload the SpecRunner.html page again. You will see Figure 1-21.
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig21_HTML.jpg
Figure 1-21

Expectation Error

The situation has improved. Now, you are getting a real expectation error, not a JavaScript runtime error. The expectation error is telling you that your function, the encrypt() function, should have thrown an error but it hasn’t.

This is not coming with a surprise, because you have not changed, in any way, the encrypt() function to throw an error when it is given a non-Latin character.

Let’s enhance encrypt() accordingly, so that it satisfies the expectation (Listing 1-11).
function encrypt(inputString) {
    var mapping = {
        'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e', 'e': 'f',
        'f': 'g', 'g': 'h', 'h': 'i', 'i': 'j', 'j': 'k',
        'k': 'l', 'l': 'm', 'm': 'n', 'n': 'o', 'o': 'p',
        'p': 'q', 'q': 'r', 'r': 's', 's': 't', 't': 'u',
        'u': 'v', 'v': 'w', 'w': 'x', 'x': 'y', 'y': 'z',
        'z': 'a',
        'A': 'B', 'B': 'C', 'C': 'D', 'D': 'E', 'E': 'F',
        'F': 'G', 'G': 'H', 'H': 'I', 'I': 'J', 'J': 'K',
        'K': 'L', 'L': 'M', 'M': 'N', 'N': 'O', 'O': 'P',
        'P': 'Q', 'Q': 'R', 'R': 'S', 'S': 'T', 'T': 'U',
        'U': 'V', 'V': 'W', 'W': 'X', 'X': 'Y', 'Y': 'Z',
        'Z': 'A'
    };
    var result = "";
    for (var i = 0; i < inputString.length; i++) {
        var encryptedChar = mapping[inputString[i]];
        if (encryptedChar === undefined) {
            throw new ArgumentError("non-latin alphabet character encountered");
        }
        result = result + encryptedChar;
    }
    return result;
}
Listing 1-11

encrypt() Code That Is Throwing an Error

In this code, you are enhancing the for loop to check for the input character to be encrypted. If the input character is not mapped, this means that it is not part of the list of characters that the function can encrypt. In that case, it throws an ArgumentError.

Save the preceding code and reload the page SpecRunner.html. You will see the following (Figure 1-22).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig22_HTML.jpg
Figure 1-22

All Specs Are Running Successfully

Perfect! All specs are running successfully. You can enhance the spec that checks for invalid characters if you want, in order to cover for more examples with invalid characters. This is left as a small exercise to you.

Refactoring

You have finished the implementation of your encrypt() function. It is ready. It does what it is required to do, and all your specs are running successfully.

Now that you have done that, you can freely refactor the code of the encrypt() function to be easier to read and maintain (Listing 1-12).
function ArgumentError(message) {
    this.name = 'ArgumentError';
    this.message = message || 'Argument Error';
    this.stack = (new Error()).stack;
}
ArgumentError.prototype = Object.create(Error.prototype);
ArgumentError.prototype.constructor = ArgumentError;
function encrypt(inputString) {
    var mapping = {
        'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e', 'e': 'f',
        'f': 'g', 'g': 'h', 'h': 'i', 'i': 'j', 'j': 'k',
        'k': 'l', 'l': 'm', 'm': 'n', 'n': 'o', 'o': 'p',
        'p': 'q', 'q': 'r', 'r': 's', 's': 't', 't': 'u',
        'u': 'v', 'v': 'w', 'w': 'x', 'x': 'y', 'y': 'z',
        'z': 'a',
        'A': 'B', 'B': 'C', 'C': 'D', 'D': 'E', 'E': 'F',
        'F': 'G', 'G': 'H', 'H': 'I', 'I': 'J', 'J': 'K',
        'K': 'L', 'L': 'M', 'M': 'N', 'N': 'O', 'O': 'P',
        'P': 'Q', 'Q': 'R', 'R': 'S', 'S': 'T', 'T': 'U',
        'U': 'V', 'V': 'W', 'W': 'X', 'X': 'Y', 'Y': 'Z',
        'Z': 'A'
    };
    var encryptChar = function(inputChar) {
        var encryptedChar = mapping[inputChar];
        if (encryptedChar === undefined) {
            throw new ArgumentError("non-latin alphabet character encountered");
        }
        return encryptedChar;
    };
    return (
        inputString.
          split('').
          map(encryptChar).
          join('')
    );
}
Listing 1-12

Refactor the encrypt() Function

As you can see here, you defined a local function encryptChar, and you have also got rid of the for loop. You are now using split(), map(), and join() JavaScript functions instead.

If you save the preceding code and reload the SpecRunner.html, you will see that all the specs are still running successfully. This makes you feel certain that your refactoring changes didn’t break the functionality of the encrypt() function.

TDD Flow

During your function development, you have followed a specific flow of development:
  1. 1.

    You have introduced the specs.

     
  2. 2.

    You saw the specs failing.

     
  3. 3.

    You have inserted the minimum amount of code to make the specs successful.

     
  4. 4.

    You have refactored the code.

     
This is the TDD (Test-Driven Development) flow of work. It is depicted on the following diagram (Figure 1-23).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig23_HTML.png
Figure 1-23

TDD Flow of Development

It is very important to understand this flow. TDD helps you develop a software module that is according to the requirements. Also, the end of the process is covered with tests, which means that further development or refactoring will never introduce any regression bugs without being noticed.

There is plenty of documentation on the Internet and lots of books about how to apply TDD properly.

Jasmine Matchers

Up until now, you have used two Jasmine-specific functions, the toEqual() and the toThrowError() . These are called Jasmine matchers.

There are plenty of Jasmine matchers that you can use. Read the following documentation so that you can see what each Jasmine matcher can do: https://jasmine.github.io/api/3.5/matchers.html.

Some indicative and commonly used matchers are as follows:
  1. 1.

    The toBe() matcher compares with ===.

     
  2. 2.

    The toMatch() matcher is for regular expressions.

     
  3. 3.

    The toBeDefined() matcher compares against undefined. It returns true if the actual expression is not undefined.

     
  4. 4.

    The toBeUndefined() matcher compares against undefined. It returns true if the actual expression is undefined.

     
  5. 5.

    The toBeNull() matcher compares against null. It returns true if the actual expression is null.

     
  6. 6.

    The toContain() matcher is for finding an item in an array.

     
  7. 7.

    The toBeLessThan() and toBeGreaterThan() matchers are for mathematical comparisons.

     

And you can always negate the matcher behavior by prefixing with a method call to not. For example, not.toMatch() returns true if the actual expression does not match the expected expression given.

Setup and Teardown

There are cases in which some specs in a suite (inside a describe block) might perform common things to set up their context and common things to tear it down.

If there are things that need to be executed before each spec (setup phase), you can put them once inside a beforeEach() block.

This is an example JavaScript code:
var globalInteger = 0;
function addThis(value) {
   globalInteger += value;
}
Then you can have a Jasmine test suite as follows (Listing 1-13).
describe("Function addThis to add a value", function() {
      beforeEach(function(){
          globalInteger = 5;
      });
      it("returns 10", function() {
          addThis(5);
          expect(globalInteger).toEqual(10);
      });
      it("returns 20", function() {
          addThis(15);
          expect(globalInteger).toEqual(20);
      });
});
Listing 1-13

Example with beforeEach

Try That in a JS Bin
You can try Jasmine in a JS Bin, without having any local page code for Jasmine and SpecRunner.html. How can you do that? You start a new JS Bin, and you make sure that you reference Jasmine libraries in the head section of your HTML part:
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/jasmine.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/jasmine-html.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/boot.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/jasmine.min.css"></head>

The preceding code is referencing the Jasmine library from the Cloudflare CDN network. Then you need to write the JavaScript code under test. That one, you write it inside the head section, using the <script> element. Then you need to write the Jasmine specs that test your JavaScript code. You use the <script> element too.

See Figure 1-24.
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig24_HTML.jpg
Figure 1-24

Jasmine Through a JS Bin

At the beginning of each spec and before the it is executed, the beforeEach block will be executed. This, in your preceding example, will set the global variable globalInteger to 5.

Like beforeEach(), one can use a teardown function by calling the afterEach() block. Whatever you put inside the afterEach() will be executed after each it block.

Another way you can share state between beforeEach(), it(), and afterEach() is by using the object this. The this object is accessible inside those blocks; and, not only that, at the end of an it(), it is reset to an empty object in order to avoid pollution of state and ripple unexpected effects from one it() call to the next. Keep reading in order to see an example usage of this.

Nesting describe Blocks

The describe block can be created inside other describe blocks essentially creating a tree of specs. You usually do that when you want to divide a group of specs into subgroups. Subgroups might convey better the logical cohesion between the specs they include.

The following example does a heavy use of nesting of describe blocks and of beforeEach() blocks. Write this inside a JS Bin (Listing 1-14).
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>JS Bin</title>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/jasmine.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/jasmine-html.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/boot.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.5.0/jasmine.min.css"></head>
<script type="text/javascript">
    function maximumOfThree(a, b, c) {
        if (a > b ) {
            if (a > c) {
                return a;
            }
            else {
                return c;
            }
        }
        else if (b > c) {
            return b;
        }
        else {
            return c;
        }
    }
</script>
<script type="text/javascript">
    describe("Testing 'maximumOfThree'", function() {
        describe("when a is greater than b", function() {
            beforeEach(function() {
                this.a = 5;
                this.b = 4;
            });
            describe("when a is greater than c", function() {
                beforeEach(function() {
                    this.c = 3;
                });
                it("returns a", function() {
                    expect(maximumOfThree(this.a, this.b, this.c)).toEqual(this.a);
                });
            });
            describe("when a is less than or equal to c", function(){
                beforeEach(function() {
                    this.c = 6;
                });
                it("returns c", function() {
                    expect(maximumOfThree(this.a, this.b, this.c)).toEqual(this.c);
                });
            });
        });
        describe("when a is less than b", function() {
            beforeEach(function() {
                this.a = 5;
                this.b = 6;
            });
            describe("when b is greater than c", function() {
                beforeEach(function() {
                    this.c = 5;
                });
                it("returns b", function() {
                    expect(maximumOfThree(this.a, this.b, this.c)).toEqual(this.b);
                });
            });
            describe("when b is less than c", function() {
                beforeEach(function() {
                    this.c = 7;
                });
                it("returns c", function() {
                    expect(maximumOfThree(this.a, this.b, this.c)).toEqual(this.c);
                });
            });
        });
    });
</script>
<body>
</body>
</html>
Listing 1-14

Nesting describe Blocks

If you run it in a JS Bin, you will get the following (Figure 1-25).
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig25_HTML.jpg
Figure 1-25

Nested describe Blocks Generated Nested Results

As you can see, the nested describe blocks generate nested results too. This is very convenient and makes the reader of the spec results feel confident about what is specified and what is not.

Note that in the preceding example, you used beforeEach() blocks to define the setup phase of each test. The beforeEach() blocks are called in the order they are defined – first the ones defined for the outer describe blocks and then the ones for the nested describe blocks.
../images/497830_1_En_1_Chapter/497830_1_En_1_Fig26_HTML.jpg
Figure 1-26

Nested describe and beforeEach Blocks – Order of Execution

As you can see from the preceding Figure 1-26, the test at point (3) is called after the beforeEach() at point (2), which is called after the beforeEach() block at point (1).

Disabling Suites and Pending Specs

There are times in which you want your group of specs, your test suite, to be disabled. Or you might want some of your specs to be considered as incomplete, pending completion.

In order to disable a suite of specs, use the function xdescribe() instead of describe(). Similarly, in order to mark a spec as pending, use the method xit(), instead of it().

Tasks and Quizzes

Task Details
  1. 1.

    You need to implement a JavaScript function using TDD methodology and the Jasmine specs.

     
  2. 2.
    Here are the requirements of the function:
    1. 1.

      It should have the name mixStrings.

       
    2. 2.

      It should take two string arguments.

       
    3. 3.
      It should mix the arguments and return the mixed string. Mixing logic is the first char from the first string, then the first char from the second string, then the second char from the first string, then the second char from the second string, and so on. Examples are as follows:
      1. 1.

        If the first string is "foo" and the second string is "bar", the result string should be "fboaor".

         
      2. 2.

        If the first string is "f" and the second string is "b", the result string should be "fb".

         
       
    4. 4.
      Special provision should be taken for the following edge cases:
      1. 1.

        The first string is empty "" or null or undefined and the second string non-empty. It should return the second string.

         
      2. 2.

        The first string is non-empty and the second string is empty "" or null or undefined. It should return the first string.

         
      3. 3.

        When both strings are empty "" or null or undefined, it should return an empty string "".

         
       
     
  3. 3.
    We give you some hints in order to help you:
    1. 1.
      The following is an indicative spec runner result (Figure 1-27).
      ../images/497830_1_En_1_Chapter/497830_1_En_1_Fig27_HTML.jpg
      Figure 1-27

      Indicative Spec Runner Results

       
     
  1. 2.

    Maybe you would like to use nested describe and beforeEach() blocks to set up the context of your tests.

     
  2. 3.

    Note that you may use expect() or fail() inside the beforeEach() blocks too, in order to make sure that you have set up the context of your tests correctly.

     
  3. 4.

    You may want to use the this object in order to share state between beforeEach() and it() blocks.

     

Key Takeaways

  • How to install Jasmine

  • How to set up the SpecRunner page

  • How to write your expectations and then execute them in the runner

  • How to follow the TDD flow of development

  • How to use Jasmine in a JS Bin

In the following chapter, you will learn about stubbing and mocking and how you can use these very important techniques with Jasmine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.214.32