In this chapter, you are going to learn Test-Driven Development (TDD) using Jasmine.
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.
Learning Goals
- 1.
Learn how to download Jasmine.
- 2.
Learn how to set up your local folders to work with Jasmine.
- 3.
Learn how to set up your spec runner HTML page.
- 4.
Learn how to write specs for your JavaScript code.
- 5.
Learn how to interpret the results that you see being output at the spec runner page.
- 6.
Learn about the describe function.
- 7.
Learn about the it function.
- 8.
Learn about the expect function.
- 9.
Learn about Test-Driven Development (TDD).
- 10.
Learn how to transfer all your requirements to corresponding tests.
- 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.
Learn about the most important Jasmine matchers.
- 13.
Learn about setup and teardown spec phases.
- 14.
Learn how to try Jasmine on the JS Bin platform.
- 15.
Learn how you can nest a describe block within another.
- 16.
Learn how you can disable a suite of specs.
- 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
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
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.
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.
Setting Up Jasmine
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.
Bare-Bones SpecRunner.html Page Content
This is basically an empty body HTML page, and it works completely with JavaScript.
- 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.
For the JavaScript spec (a.k.a. test) files themselves.
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.
Initial Content of the my-application-specs.js File
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.
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.
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.
First Real Expectation
- 1.
That your function name will be encrypt
- 2.
That it will be taking one string parameter
- 3.
That when called with 'a' as argument, it will return the string 'b'
That was expected. The encrypt function is not defined yet. You need to define this function in your project.
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.
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.
More Expectations
expect(encrypt('a')).toEqual('b');
expect(encrypt('b')).toEqual('c');
expect(encrypt('c')).toEqual('d');
And so on
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.
Better Implementation of the encrypt Function
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.
The preceding expectation needs to be added after the existing it and before the closing }); of the describe.
Hence, you now have to visit, again, the code of the encrypt() function and make it work for words too.
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.
Perfect! Now your encrypt() method works both for single characters and for words.
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.
- 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.
If the input character is uppercase, then the output will be uppercase too.
- 3.
If the input character is “z”, then the output should be “a” – similarly for the “Z”.
- 4.
Only characters from the Latin alphabet will be allowed. Otherwise, an exception will be thrown.
Spec for the Last Requirement
Full Expectations Code
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.
ArgumentError Definition
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.
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.
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.
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
- 1.
You have introduced the specs.
- 2.
You saw the specs failing.
- 3.
You have inserted the minimum amount of code to make the specs successful.
- 4.
You have refactored the code.
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.
- 1.
The toBe() matcher compares with ===.
- 2.
The toMatch() matcher is for regular expressions.
- 3.
The toBeDefined() matcher compares against undefined. It returns true if the actual expression is not undefined.
- 4.
The toBeUndefined() matcher compares against undefined. It returns true if the actual expression is undefined.
- 5.
The toBeNull() matcher compares against null. It returns true if the actual expression is null.
- 6.
The toContain() matcher is for finding an item in an array.
- 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.
Example with beforeEach
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.
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.
Nesting describe Blocks
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.
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
- 1.
You need to implement a JavaScript function using TDD methodology and the Jasmine specs.
- 2.Here are the requirements of the function:
- 1.
It should have the name mixStrings.
- 2.
It should take two string arguments.
- 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.
If the first string is "foo" and the second string is "bar", the result string should be "fboaor".
- 2.
If the first string is "f" and the second string is "b", the result string should be "fb".
- 1.
- 4.Special provision should be taken for the following edge cases:
- 1.
The first string is empty "" or null or undefined and the second string non-empty. It should return the second string.
- 2.
The first string is non-empty and the second string is empty "" or null or undefined. It should return the first string.
- 3.
When both strings are empty "" or null or undefined, it should return an empty string "".
- 1.
- 1.
- 3.We give you some hints in order to help you:
- 1.The following is an indicative spec runner result (Figure 1-27).
- 1.
- 2.
Maybe you would like to use nested describe and beforeEach() blocks to set up the context of your tests.
- 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.
- 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.