Learn basic testing tutorial for Jasmine and TypeScript

This tutorial gives an introduction to use TypeScript along with Jasmine for writing the unit tests in JavaScript. We will come to know the importance of generating and crafting standard unit tests. One does not need the knowledge of Typescript or Jasmine to dive in here. A basic idea of JavaScript only will help to get familiar with the ins and outs of the tutorial. In this tutorial, we will get to know how to create standard unit tests in Jasmine as well as how to boost the output by the concept of Typescript. Here, we will learn the simple interface of the Jasmine in order to build a great test code along with the results.

What is Unit Testing?

Unit testing means testing each unit of the code written. We all know that a unit is a small testable part or function in a particular environment. 

The ultimate goal of unit testing a program or a piece of code is to segregate the parts and test individually whether it is working efficiently and expectedly or not. The basic idea behind the concept of unit testing is nothing but to create test cases for each of the components present. Thus, it is important to perform smart tests. In the process, the automation reduces the difficulties of picking the errors present in the complex part of the application and improves the coverage of the test as proper attention is given in all the units. To get higher efficiency one can use mock objects while testing. It is a good idea to refactor the tests whenever you refactor the specific code. Developers often follow the rule of writing the test before fixing the bugs or errors.

Why is Unit testing important?

  1. Process becomes agile: Unit testing makes the process of coding more agile. While adding new features sometimes old design and codes are required to change.
  2. Improves code quality: Before sending the code to the next level, the process of unit testing identifies the defect in the code. Thus, saves time and improves the quality at the same time.
  3. Early finding of the software bugs: Unit testing helps to find the bugs at an early stage and can be settled without affecting other pieces of the program.
  4. Leads to the verification of each small unit: The accuracy of the individual unit is tested and verified. Thus bugs are resolved if found for separate units.
  5. Documentation of the system becomes easy: Unit testing provides the system’s documentation that helps to the basic understanding of the interface of the unit. 
  6. Unit testing simplify the debugging process:  In case the test fails, the final changes are the only thing required to be debugged.
  7. Helps in the proper implementation of the design: It is important to write the test part initially which in turn gives a thorough idea of the design. This helps to create good designs.
  8. Reduces the cost of fixing the bugs: As the unit testing helps to check for the bugs at an early stage, the cost of bug fixation is reduced. If the bugs are discovered at later stages, the cost of fixing those bugs would be very high. Thus unit testing is important.

Now, let us get familiar with some of the pitfalls of unit testing.

The pitfalls of unit testing

At times when the changes are implemented into the code the breakdown of the test. This should not be done as the test undergone is expected to show failure in the code, but not a failure in the test performed. This scenario is referred to as the false positive. Complex tests make the process tough for the developers to continue with the flow. It takes a lot of time to understand the cause of failure or error in case that occurs while running the long and complex tests. The above discussed two factors give rise to the third category called ignored tests. Ignored tests are the one that fails continuously. Repetitive failure in tests makes the developers name them as ignored tests. In order to make it work, we need to use standard coding principles to the test code. It is important to write the tests with full severity and rigor to make it successful. You need to start testing things that matter in order to get the expected output. Now let’s check out how Jasmine adds flavour to your testing outcomes and efforts.

Jasmine – the ultimate flavor

Jasmine is an awesome testing framework with an easy to read syntax that runs on JavaScript enabled platforms. It works like a behavior Driven Development platform and the functions used here makes the code super easy to maintain and follow. We all know that it is required to optimize the tests. We will learn how to optimize them as we get into the tutorial. Let’s talk about certain functions that are frequently used in the code. For example, the describe function, that enables the grouping of tests. It defines the parameters used in the test as well as the groups and enclosure that contains the execution steps. In case of any requirement, subgroups can also be formed. Describe function helps to create and design a simple test structure because of its feature of nesting. Next, another function is ‘it’ which is used to define the actual test. It tells us about the behavior of the code. The syntax if the function is it()  and it indicates Jasmine what it( should be the output of the test).

 Let’s understand in an easy way :

it(“should be 9 if I multiply 3 with 3”, function() { expect ( 3*3). to be(9) ;});

 The expect function is the assertion and it shows the actual value to test. The function SpyOn helps in mocking as well as it allows us to separate the unit from the rest. The beforeEach function is used and run for all the tests performed within the group. Developers often use nesting features of forEach and describe functions to make their program simple and understandable. Now, let’s move ahead and learn something about typescript.

Productivity Boosting by TypeScript

Typescript is developed by Microsoft and is regarded as the superset of JavaScript. It is a programming language that is designed for developing big applications. It adds static type definition as it is considered as the world’s most useful tool. The Angular team has built the world-renowned framework with the help of the typescript as it enhances productivity. It is mainly famous for its static typing potentiality. It helps in designing and documentation. 

Let’s call out a few features of TypeScript:

  1. It provides the class as well as module support.
  2. It helps in Static checking.
  3. It gives the support of ES6 features.
  4. Helps in the packaging of JavaScript.
  5. Helps in enhancing the coding capability and understandability.

Typescript is a great platform from where we can take the advantage of static typing through the JavaScript. It helps in improving and boosting the performance of the entire team. The use of types enables us to code easily and efficiently. 

Now, let us take a look at the demo project and learn how the tools help to get familiar with the tools.

Setting up project environment with Jasmine

First of all, in order to write tests in the Typescript, we need to set up the ‘project environment’ with Jasmine. Make sure you already have Node.js installed in your system and follow below steps:

  1. Create a folder and package.json
  2. Install Typescript and generate tsconfig.json
  3. Install Jasmine, it’s type definition and Jasmine to Typescript reporter
  4. Create jasmine.json and configure it
  5. Create helper.js

1. Create a folder and package.json

Create a folder called jasmine-ts. Go inside this folder and open command prompt by typing cmd in the address bar of the window explorer. In the command prompt type below command:

npm init

When prompted keep hitting enter key. When it is done, it will create a package.json file.

2. Install Typescript and generate tsconfig.json

Let us install latest Typescript by typing and generate the tsconfig.json which has the configuration required for the Typescript compiler.

npm i [email protected] -D 
npx tsc --init --moduleResolution node --target ES2017 --sourceMap --module commonjs --removeComments --outDir dist

Here, “dist” folder is used to store the compiled JavaScript files.

3. Install Jasmine, it’s type definition and Jasmine to Typescript reporter

Now we need to install Jasmine, Jasmine type definition and Jasmine to Typescript reporter:

npm i jasmine @types/jasmine -D
npm i jasmine-ts-console-reporter -D

Based on the typescript files, errors will be recorded by the Jasmine typescript console reporter. Instead of the JavaScript files, the typescript files will be used to refer types as well as the location of the test failed. 

4. Create jasmine.json and configure it

Initialize jasmine in your project which will generate jasmine.json under spec/support folder.

npx jasmine init

Open jasmine.json and update spec_dir property to dist and helpers property to ../spec/helpers/**/*.js. Now your jasmine.json will looks like below:

//-------------------------------------------------
// spec/support/jasmine.json
//-------------------------------------------------
{
  "spec_dir": "dist",
  "spec_files": [
    "**/*[sS]pec.js"
   ],
  "helpers": [
   "../spec/helpers/**/*.js"
  ],
  "stopSpecOnExpectationFailure": false,
  "random": true
}

5. Create helper.js

It is important to create special helper in order to enable ‘Jasmine to typeScript’ console reporter. Let us create helper.js under spec/helpers folder. Add below code snippet to helper.js.

//-------------------------------------------------
// spec/helpers/helper.js
//-------------------------------------------------

const TSConsoleReporter = require("jasmine-ts-console-reporter");

jasmine.getEnv().clearReporter(); //Clear default console reporter
jasmine.getEnv().addReporter(new TSConsoleReporter());

Your project environment is now ready to use.

Prepare Test Code

Now let us write some program logic as well as tests.

Below is our simple UserService class created under src folder and named the file as user-service.ts

//-------------------------------------------------
// src/user-service.ts
//-------------------------------------------------
export class UserService{
  constructor(){}

  public getUsers(): any[]{
    return [];
  }
}

Let us create our test file named as service-api-spec.ts under the same src folder. We will write couple of test cases as given below.

//-------------------------------------------------
// src/user-service-spec.ts
//-------------------------------------------------
import { UserService } from './user-service';

describe("UserService getUsers method test case", ()=> {
  // For storing UserService instance
  let userService: UserService;

  // For every test case we need UserService instance so before running each test case the UserService instance will be created
   beforeEach(() => {
     userService = new UserService();
   });
   
   // Test case to ensure getUsers method is defined
   it('Should be defined', () => {
      expect(userService.getUsers()).toBeDefined('getUsers method should be defined');
   });

   // Test case to ensure getUsers method returns Array object
   it('Should return Array', () => {
      expect(userService.getUsers()).toEqual(jasmine.arrayContaining([]), 'getUsers method should return Array object');
   });

   // Test case to ensure getUsers method returns Array containing specific object property and value
   it('Should return Array containing "name" property and value as "admin"', () => {
      expect(userService.getUsers()).toContain({'name': 'admin'}, 'getUsers method expect Array containing "name" property and value as "admin"');
   });
});

To run the Test case, first compile the Typescript to JavaScript and run the Jasmine in the commandline as below:

npx tsc
npx jasmine

Every time we run the above commands it will generate transpiled js files in the dist folder. If we rename any files and run the commands, the old files will be still there in the dist folder. To cleanup the dist folder every time we run the commands, let us installed rimraf node modules.

npm i rimraf -D

Let us setup the build script so that we can run the above commands in one command. Edit the package.json and update the scripts property of the value to rimaf ./dist && tsc && jasmine.

Now we can simply run the test case by typing npm test and will get the results of our test case execution. Now, the folder structure will look like as below:

Jasmine project folder structure
Our Jasmine project folder structure

As we have separately added “typescript console reporter” to jasmine, the errors are reported with  exact error messages and are referenced to typescript (.ts) files.

Here, there is one failure reported.

Jasmine Test Case for UserService class
Jasmine test cases result for UserService

Let’s check out the test results after correcting the UserService as shown below:

//-------------------------------------------------
// src/user-service.ts
//-------------------------------------------------
export class UserService{
  constructor(){}

  public getUsers(): any[]{
    return [{
     'name': 'admin'
    }];
  }
}

Now, you can easily check that there is no unit testing failures. 

So far, we have learned how to write test in jasmine along with its execution. We understood the usage of describes and its as well as their importance in writing test codes. We finish up the test by checking the expected output. We also learned that in case the test goes wrong, how does the output looks like and how to correct it. 

Mocking with Jasmine

It is important to learn how to mock calls the Jasmine. Developers use the Jasmine framework that enables the feature of dynamic mocking and stubbing. Here, we will learn why do we mock and how to make difference between the stubs as well as mocks. There are asynchronous and synchronous function calls that are mocked by the jasmine spies.

Why do we mock?

  1. We mock in order to isolate the object’s behaviour you want to test by replacing the other objects with mocks.
  2. Mocks are utilized to simulate the real object’s behaviour.
  3. Mocks are used to test a particular class we need to make it isolated from others. They are using the methods of other classes. Now we can simulate the behaviour of the required class by the mocking frameworks. At the same time, they are used for unit testing for the purpose of isolation. These are the scenarios where stubs and mocks come into display.
  4. Both stub and mock have the same interface but differ in the functionality. 
  5. Stubs do not affect the code directly. These are the objects made to hold the already defined data value and those values are called during the test. They just act as a place holder for something in order to run the code properly.
  6. Mocks assist us to maintain the focus upon the units to be tested.
  7. They are used for providing fake test results for REST APIs without giving a call to the real endpoints. This helps to tests the code independently.

How to write Tough Tests with Jasmine

Here, we will get to know the importance of the tough test scenario. We will also learn to make the tests ineffective to the unnecessary changes in the application. First of all, we will start with the illustration that shows how do the tough tests appear. By the word ‘tough’ test, we mean a reliable test. It is the relevant test which exactly knows what unit it is testing and it is focused on. It reports the issues if any and is much easier to work upon. Tough tests are the only required test for performing unit testing. On the other hand, fragile tests are just the opposite and irrelevant. The simple and easy test carries a lesser probability of failure.  Jasmine can perform black-box tests as well as White-box tests. The best option for black-box tests is considered to put the config data for external dependencies to a config file. 

For REST API, the config files can be usernames, URLs, or passwords. Performing Rest API tests are considered as the most common level of integration tests.

The RESTful Response

Let’s talk about the situation when the HTTP service gives back an invalid output or response (Status 500), which indicates there is an issue with the server. The validation service is expected to return some valid output to give the HTTP call. After that, the service call is set up in order to grab the error data. With the help of the test variable, we can check for the status of 500. The very next step is to check for a call to the translator spy. The translator performs the function of returning back the data from the service. It translates the thing into the required JSON object. In case of an invalid HTTP response, the service should not be called. 

The evil twins: False Negative and False Positive

Now it’s time to talk about the test results that can be positive as well as negative. Another category which consists of the false negative and false-positive test results are named to be evil twins. 

The result shows that a given condition has been successfully fulfilled when it has actually not. Then this scenario is referred to as the false positive. 

A false-negative test result occurs where a test result shows that the condition is not fulfilled while it was successful.

In case your program code is broken and the test flows then no alarm is raised with no effect. This defines the false-negative scenario.

If the program code is right while the test performed is failed causes an alarm to get raised. This defines false positives.

Let us try to understand in a simpler way: 

A Positive test – positive to a bug.

A Negative test- negative to a bug.

A False Positive– code is bug-free still test fails.

A False negative– code is not bug-free still test passes.

Positive Test results

Now we know that no signal is thrown in case of negative test results. A positive test output indicates that a signal is thrown. It clearly indicates that something went wrong and some action needs to be taken. A False positive test scenario means the signal is thrown while there is no problem in the code. When a failure occurs in the test with no issues in the code A False Positive state is defined. Due to the false positive states, there are chances to occur numb in the positive signals. In case we face a large number of false positive test results, we should consider that everything is in false positive condition in spite of checking the entire code. If we try to understand the false negative output, we need to assume that there must be some problem , and there is no signal thrown. In unit testing, this state occurs when all the test performed has passed while the code consists of some errors. If the error persists, the user faces the issue and then it needs to get checked thoroughly by the process of additional manual testing. If you want to avoid the false negative cases then it is important to test the positive signal part. You can also take the help of TDD ( Test Driven Development ) in order to prevent the false negatives. If you are using TDD, there, before the code, a test must be written. First, you need to run the test, watch it get Failed. Now to manage the test specification, code is necessary to be written, rerun it and watch it to get Passed. In case if you are not using the TDD method, there is another way to check the positive signal is by short-circuiting the method that is under test. To check this a return value is used at the beginning of the method. Since nothing is going on in the method, this would make all the tests get failed. So now delete the line and then run the tests a second time. So, this is understood that when Jasmine makes changes in the framework, they started to warn about the false negative potentials.

Summary

This accomplishes our tutorial on ‘ Learn basic testing tutorial for Jasmine and TypeScript’. I hope you understand the tutorial and are satisfied with it. We learned about the unit testing and why is it so important to run high-quality unit testing. We learned about TypeScript and Jasmine. We got familiar with the functions that are used in the specific scenarios. Here, screenshots are shared that explains the output of the test performed with error and after correcting the error. We get to know about the optimization skills to make the tests simple. In this tutorial, the Jasmine mocking is described with examples that show the importance of isolation in testing. Why is it important to mock is also illustrated above to clear the concept in a better way. We learned to be focused on testing the required zone in complex interactions. Finally, we got to know about the test results i.e.; all the possible combinations like positive and negative test results. Tips to avoid the pitfalls of false test signals are explained. In the above tutorial, you have learned to write tough tests with Jasmine. Considering your valuable time, hope you would be able to write a meaningful code of your own after completing the tutorial.

Mapu

I am a technology freak with passion for UI technology.

Leave a Reply