Testing TypeScript Node app with Jasmine

Setup Jasmine testing framework with Node and TypeScript

My typescript project setup is described in this article. To be able to write tests in typescript with Jasmine framework we need to setup project environment with some Jasmine specifics.

This scaffold project is also available on the github.

Initialize typescript compiler and linter :

npm install typescript@latest --save-dev
tsc --init --moduleResolution node --target ES2017 --sourceMap --module commonjs --removeComments --outDir dist 

npm install tslint --save-dev
tslint --init

npm install @types/node --save-dev

Compiled javascript files for code and jasmine test specifications will be saved to “dist” folder.

Now we need to install Jasmine and jasmine to typescript reporter:

npm install jasmine @types/jasmine --save-dev
npm install jasmine-ts-console-reporter --save-dev

Jasmine typescript console reporter (jasmine-ts-console-reporter) will report errors based on typescript files instead of javascript files. It will tell you which test and the location in the file failed referring to typescript files instead of javascript files.

Initialize jasmine in your project :

./node_modules/.bin/jasmine init

Change generated jasmine.json to match this one:

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

Create special helper to enable “jasmine to typescript” console reporter. In the spec/helpers folder add new file “ts_console.ts” with the content:

// tslint:disable-next-line:no-var-requires
const TSConsoleReporter = require("jasmine-ts-console-reporter");

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

Prepare test code

Write some program logic and tests:

export class MyApi {
    public getName(): string {
        return "";
    }
}

To test this simple api we create “myapi-spec.ts” file in the spec folder:

import {MyApi} from "../src/MyApi";

describe("MyApi getName function return value", () => {
    it("Should be defined.", () => {
        const myapi = new MyApi();
        expect(myapi.getName()).toBeDefined("The function getName() should be defined.");
    });

    it("Should't return blank.", () => {
        const myapi = new MyApi();
        expect(myapi.getName()).not.toMatch("", "The function getName() should return the name.");
    });

    it("Should return 'MyName'" , () => {
        const myapi = new MyApi();
        expect(myapi.getName()).toMatch("MyName");
    });

} );

The full project structure:

To compile all typescript to javascript run “tsc” command in the root of the project.

tsc

And to run jasmine tests we run locally installed jasmine with the command:

./node_modules/.bin/jasmine

If everything is correct, jasmine will report result of the run in the console:

Started
.FF

Failures:
1) MyApi getName function return value Should't return blank.
  Message:
    Expected '' not to match '', 'The function getName() should return the name.'.
  Stack:
        at UserContext.it (c:\Bisaga\Workspaces\learning\mytest\spec\myapi-spec.ts:11:36)

2) MyApi getName function return value Should return 'MyName'
  Message:
    Expected '' to match 'MyName'.
  Stack:
        at UserContext.it (c:\Bisaga\Workspaces\learning\mytest\spec\myapi-spec.ts:16:32)

3 specs, 2 failures
Finished in 0.025 seconds

Because we add additional “typescript console reporter” to jasmine, the errors reported are referenced to typescript (.ts) files with exact line/column and error message.

Integrate jasmine test results with Code editor

First we need to understand how vs code editor tasks works. As we see in jasmine report, each failure is described in multiple lines. The problem matcher should analyze multiple lines pattern, this is described here.

Problems tab should display errors in tests after test run

Create task for running jasmine and to intercept errors as “problems” in the visual studio code problems tab:

As we see in the screenshot, the exceptions in jasmine test results are intercepted by vs code editor and presented as “problems”.  If you click on the single problem line, the file under the error will be opened and cursor will be moved to the corresponding location.

Task

In the .vscode folder create “tasks.json” file with the jasmine command and additional logic to parse results (problemMatcher) :

{
    "version": "0.1.0",
    "isShellCommand": false,
    "showOutput": "always",
    "tasks": [
        {
            "taskName": "run test",
            "showOutput": "always",
            "command": "npm",
            "args": [
                "run", 
                "test"
            ],
            "problemMatcher": {
                "owner": "javascript",
                "fileLocation": ["absolute"],
                "severity": "error",
                "pattern": [
                    {
                        "regexp": "Message.*"
                    },
                    {
                        "regexp": "[^ ](.*)",
                        "message": 1
                    },
                    {
                        "regexp": "Stack.*"
                    },
                    {
                        "regexp": "at +.*\\((.*\\\\*.ts):(\\d+):(\\d+)\\)",
                        "file": 1,
                        "line": 2,
                        "column": 3
                    }
                ]
            }
        }
    ]
}

How to write regular expressions

If you will study the pattern matching you should remember that matcher works on the console results line by line. All patterns together (all regexp lines patterns) represent one intercepted error.

If you need to change any of patterns in the future, the regex string in the tasks.json file is written in the “escaped” form. To develop and test patterns interactively, use this interactive javascript regex tester.  You can copy specific line from the result (line by line!) into the regex tester and develop a pattern to recognize searched variables.

Un-escaped form:  at +.*\((.*\\*.ts):(\d+):(\d+)\)
Escaped form:     at +.*\\((.*\\\\*.ts):(\\d+):(\\d+)\\)

Some problems still exists:

Because the matching is done line by line, when console window is small, and the lines are wrapped, they are not consistent with the written lines from the jasmine reporter anymore. The reported errors are wrapped at the width of the console and pattern matching doesn’t recognize them as errors any more.

You can somehow mitigate that with larger terminal window or with smaller terminal window font. This way the reported errors will remain in single lines consistent with the jasmine reporter. I wish that this wrapping could be disabled but for now it is not an option.

To change the size of the font in the terminal window add this settings to the settings.json file:

    "terminal.integrated.fontFamily": "Consolas, 'Courier New', monospace",
    "terminal.integrated.fontSize": 10,

Anyway, this problem is already recognized and I hope it will be resolved in the future.

Visual Studio Code – Linux installation

Elementary OS – linux distribution

On one of my machines I use ElementaryOS linux. This flavor of linux does not support DEB or RPM packages directly so I will use tar.gz file as installation medium.

Download

Download code***.tar.gz file from https://code.visualstudio.com/Download address. Link to .tar.gz is under the “deb” square.

Install

Open terminal and go to /usr/local directory. Unpack tar.gz file into default folder and create soft-link to code application:

cd /usr/local
sudo tar xvfz ~/Download/code****.tar.gz 
ln -s /usr/local/VSCode-linux-x64/Code /usr/local/bin/code

Update

To update with a new version just download latest version of tar.gz package and unpack it to the same location. It is wise to delete (or rename) original folder first and then unpack new one.

sudo mv VSCode-linux-x64/ VSCode-linux-x64-OLD/
sudo tar xvfz ~/Download/code****.tar.gz 
sudo rm -rf VSCode-linux-x64-OLD/

Run and keep shortcut in the dock

We run code editor with “code” command. With the right click on the running program icon (in the dock), we change settings for “keep in Dock” and the program shortcut will stay in the dock.

 

 

 

 

 

 

Setting up the Environment for Node.js and TypeScript

To be productive as a programmer the write/run/debug cycle should be as fast as possible.

Because I am a little spoiled by angular-cli setup for client side web development I search for similar experience in the server side. It means that transpiling the typescript to javascript and node server restarts should be automatic and with hassle free debugging.

Install prerequisites

To create basic node application in the current folder use this command :

npm init

Install additional features needed for the application:

npm install typescript --save-dev
npm install @types/node --save-dev
npm install nodemon --save-dev

If you wish to write with express framework you will need additional libs:

npm install express --save
npm install @types/express --save-dev

 

The structure of the project folder

Server application is running in completely different project (folder) as angular client application. To develop client and server I will run two instances of vscode editors, each open corresponding folder.

The server folder contains this base structure:

I created src sub-folder where all source files will reside. For now I put index.ts file in it.

Setup typescript compiler

Create tsconfig.json file in the root folder of the project :

{
    "compilerOptions": {
        "target": "es2017",
        "module": "commonjs",
        "moduleResolution": "node",
        "noEmitOnError": true,
        "noImplicitAny": true,
        "experimentalDecorators": true,
        "sourceMap": true,
        "baseUrl": ".",
        "paths": {
            "*":[ 
                "src/types/*"
            ]
        },
        "sourceRoot": "src",
        "outDir": "dist"
    },
    "typeRoots": [
        "node_module/@types"
    ],
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "dist",
        "node_modules"
    ]
}

Compile and run the node application

Transpile (compile) typescript into javascript and watch any changes (use local tsconfig.json configuration):

tsc --watch

Start node with debug (inspect) and in auto restart mode (nodemon) :

nodemon --inspect ./dist/index.js --watch dist

Create combined npm command in package.json file

To specify multiple commands in the npm  under one script command, divide them with && character. Let’s create a “server” command:

"server": "tsc --watch | nodemon --inspect ./dist/index.js --watch dist",
  •  “tsc“: start typescript compiler in watch mode,
  •  “|” : pipe character allow to add aditional command in same script
  • nodemon“:  this command will start and restart “node” server after each detected change made by tsc compiler. Nodemon watch destination folder because it need to restart only after javascript files are changed (after transpile).

Before running nodemon for the first time in the project, you need to have compiled index.js at the required place (“dist folder”) so you need to compile at least first time the whole application manually (run tsc from the command line). After that initialization the watchers will watch for changes in source file automatically.

Run combined command from the terminal:

npm run server

Be aware of the small problem, if you ran the server inside vscode terminal window and then close the editor after that, without canceling the running process (with Ctrl+C), the node server will not automatically stop. You will need to find it between the running processes and stop it manually. To eliminate this obstacle create task and run combined command as task.

Tasks

To ease start and stop of “npm run server” command and to have better control over all started processes, create a task and a task runner for the “server” command.

if you do not have “.vscode” folder  and tasks.json file yet you create it with /Tasks/Configure tasks menu option.

Inside tasks.json file (in .vscode folder) define the command in that form (use version 0.1.0, the version 2.0.0 has problem with task termination on exiting editor):

{
    "version": "0.1.0",
    "isShellCommand": false,
    "showOutput": "always",
    "tasks": [
        {
            "taskName": "run server",
            "isBackground": true,
            "showOutput": "always",
            "promptOnClose": true,
            "command": "npm",
            "args": [
                "run", 
                "server"
            ]
        }
}

To run the task in vscode editor, open Tasks menu, open Run task and select run server task.

When you want to terminate the task, it means that stop will terminate the tsc instance and all node instances started by the “server” command.  From the Tasks menu select  Terminate task… .

If you close the editor with File/Exit , the program should also ask to terminate still running task.

If you omit “promptOnClose” setting you will not be interrupted at the exit from the editor and the processes will close as expected.

 

Keyboard shortcuts

Find command (F1)  “Open keyboard shortcuts file” and add definition to it.

// Place your key bindings in this file to overwrite the defaults
[
    {
        "key": "ctrl+t",
        "command": "workbench.action.tasks.terminate",
        "args": "run server"
    },
    {
        "key": "ctrl+r",
        "command": "workbench.action.tasks.runTask",
        "args": "run server"
    }
]

Press F1 and search for keyboard … The file is located in the Roaming folder in usual windows users location.

 

Create attach debuger command

To intercept debug breakpoints in code you need to attach to running node process with a debugger session in the vscode editor. Create new “attach” launch configuration in .vscode/launch.json file:

...
 "configurations": [
        {
            "type": "node",
            "request": "attach",
            "name": "Attach by Process ID",
            "processId": "${command:PickProcess}",
            "outFiles": [
                "${workspaceRoot}/dist/**/*.js"
            ],
            "skipFiles": [
                "node_modules/**/*.js",
                "<node_internals>/**/*.js"
            ]
        },        
...

Start debugging with “Attach by Process ID” configuration, then select the running node server instance . You can use F5 key when staying in the source file.

Now execution should stop at defined breakpoints as expected.

Debugging doesn’t stop the tsc & nodemon processes ! In fact you are only attached to your running server process. If you detach from it, you will be able to work forward as you never dive into debugging.  If you change any typescript file and save the file, watchers will detect change and restart all processes. Though you will be automatically detached from the debugging session.

Sample index.ts file

The file is a simplest REST service example in express/node program written in typescript, needed to test compiling, debugging environment.

import  * as express from 'express';

const app: express.Express = express();

app.get('/', (req: express.Request, res: express.Response) => {

    let person = { name : 'Igor Babič' };

    person = Object.assign(person, req.query);

    res.json(person);
});

app.listen('3002',() => console.log('Server listening on port 3002'));

Final version of package.json file:

{
  "name": "wodia",
  "version": "1.0.0",
  "description": "Work Diary",
  "main": "dist/index.js",
  "scripts": {
    "server": "tsc --watch | nodemon --inspect --watch dist",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Igor Babic",
  "license": "MIT",
  "dependencies": {
    "express": "^4.15.4"
  },
  "devDependencies": {
    "@types/express": "^4.0.36",
    "@types/node": "^8.0.24",
    "nodemon": "^1.11.0",
    "typescript": "^2.5.1"
  }
}

Not sure if is important in this setup, but I use next workspace vscode settings:

{
    "search.exclude": {
        "**/node_modules": true,
        "**/dist": true
    },
    "typescript.referencesCodeLens.enabled": true,
    "tslint.ignoreDefinitionFiles": false,
    "tslint.autoFixOnSave": true,
    "tslint.exclude": "**/node_modules/**/*" 
}

And the user settings for vscode :

// Place your settings in this file to overwrite the default settings
{
    "editor.fontFamily": "Consolas, 'Courier New', monospace",
    "editor.fontSize": 14,
    "window.zoomLevel": 1,
    "workbench.colorTheme": "Default Dark+",
    "terminal.integrated.shell.windows": "C:\\Programs\\PortableGit-2.12.0\\bin\\bash.exe",
    "terminal.integrated.shellArgs.windows": ["--init-file", "c:\\Users\\igorb\\.bash_profile"],
    "git.enableSmartCommit": true,
    "workbench.startupEditor": "newUntitledFile",
    "editor.renderWhitespace": "none",
    "window.restoreFullscreen": true,
    "window.restoreWindows": "all"
}

Installed visual studio code extensions


Setup GIT BASH as internal terminal inside code

Download and install portable GIT Bash .

Create .bash_profile in /Users/_user_name_/ folder.

echo "RUN: .bash_profile | GIT: SSH agent | PATH: ./node_modules/.bin"
env=~/.ssh/agent.env

agent_load_env () { test -f "$env" && . "$env" >| /dev/null ; }

agent_start () {
    (umask 077; ssh-agent >| "$env")
    . "$env" >| /dev/null ; }

agent_load_env

# agent_run_state: 0=agent running w/ key; 1=agent w/o key; 2= agent not running
agent_run_state=$(ssh-add -l >| /dev/null 2>&1; echo $?)

if [ ! "$SSH_AUTH_SOCK" ] || [ $agent_run_state = 2 ]; then
    agent_start
    ssh-add
elif [ "$SSH_AUTH_SOCK" ] && [ $agent_run_state = 1 ]; then
    ssh-add
fi

unset env

export PATH="$PATH:./node_modules/.bin"

 

Add current project node modules binary folder to the path

Add at least “./node_modules/.bin/” folder to bash profile, to be able to call locally installed modules directly inside internal terminal window. The internal terminal window will open in the project root folder.

export PATH="$PATH:./node_modules/.bin"

To be able to use git bash terminal inside visual studio code we add the terminal setup command in user settings:

"terminal.integrated.shell.windows": "C:\\Programs\\PortableGit-2.12.0\\bin\\bash.exe", 
"terminal.integrated.shellArgs.windows": ["--init-file", "c:\\Users\\igorb\\.bash_profile"],

Don’t forget to define .bash_profile file in the shell arguments.

After everything is properly set up you will be able to use internal projects node programs directly from the command line:

How to setup git to run with ssh I described here.

 

Angular and Git project setup

Angular-CLI, VSCode and Git/GitHub configuration

I will create Angular 4 client application with project setup needed to seamlessly develop and store source code to remote Github repository.

Projects folder structure

I will create client side SPA application as client to Java spring boot server application, created in previous blog article.

Both projects (server & client) are in separate folders, only at the deployment stage they will be combined under java server application. In the simplest micro-service deployment scenario, both applications are served from the same server instance. Embedded Tomcat server  will serve all files needed for angular client side application and to drive java REST services as application back-end.

myapp
!--server
!  !--SpringBootMyApp
!     
!--client
   !--AngularMyApp

Generate angular client project

After you have installed all prerequisites for angular you create a new empty application from command line. Command will create application subfolder and all required app files for simple angular app.

/myapp/client/$ ng new AngularMyApp

You can experiment with some command line switches to add additional generator options like generate routes file for example (–routing).

Dry-Run

If you do not want to actually execute commands then add –dry-run option (“-d”  for short) and inspect generated set of commands.

$ ng new app_name --routing 
$ ng new app_name --dry-run 
$ ng new app_name -r -d

Open project in Visual Studio Code

You can open project from command line (“code .”) or open folder from inside editor.

Integrate with Git  source control

After angular-cli create a project it will immediately initialize GIT folder and commit initial version of an application.

You can check log of git commits right after generating new app :

/AngularMyApp/$ git log

and result will be something like this :

Author: Angular CLI <angular-cli@angular.io>
Date:   Mon Jun 26 22:41:07 2017 +0200

chore: initial commit from @angular/cli

We see initial commit was done by @angular/cli tool.

Configure remote Git repository

If you wish to publish code to the Git-Hub server, you need to prepare local windows git to be able to push code to remote server without any username/password interaction.

git config --global credential.helper wincred
git config --global user.name "user_name"
git config --global user.email "email@example.com"

This type of user identification is used with HTTPS repository address for git communication.

You need to have github repository created (empty project!). When you create new repository, try to not create readme file, because you will not be able to push initial code up without “force” option. This option is not possible inside visual studio code. “Force push” is possible with command line or with github desktop client.

With remote repository created configure “git remote”  localy with correct “https” repository address :
/AngularMyApp/ $> git remote add AngularMyApp https://github.com/bisaga/AngularMyApp.git
Push code to Github

After adding remote repository to local git configuration you select “Push to…” inside visual studio code Git extension menu (…), select remote repository and push.

SSH key passphrase

You need to setup your ssh-agent correctly to work with remote git repository without constant interruptions with an question for ssh passphrase.

If you use bash terminal inside vscode and you work on windows, add the ssh-agent to the setup script in your home (~) folder (usually c:\Users\your_name\.bash_profile file.

env=~/.ssh/agent.env

agent_load_env () { test -f "$env" && . "$env" >| /dev/null ; }

agent_start () {
    (umask 077; ssh-agent >| "$env")
    . "$env" >| /dev/null ; }

agent_load_env

# agent_run_state: 0=agent running w/ key; 1=agent w/o key; 2= agent not running
agent_run_state=$(ssh-add -l >| /dev/null 2>&1; echo $?)

if [ ! "$SSH_AUTH_SOCK" ] || [ $agent_run_state = 2 ]; then
    agent_start
    ssh-add
elif [ "$SSH_AUTH_SOCK" ] && [ $agent_run_state = 1 ]; then
    ssh-add
fi

unset env

You can check with the command

git fetch

If you get the question then you probably need to create proper ssh-agent setup.

 

 

Links

Initial code for AngularMyApp is on this github repostory.

 

Visual Studio Code – customization

How to customize your visual studio code

Custom settings are saved in “settings.json” file, you can open it with Ctrol+Shift+P,  search for Preference: Open user settings .

Custom terminal shell – BASH

If you wish to change shell from default windows powershell to cygwin bash terminal for example, use next setting:

"terminal.integrated.shell.windows": "C:\\cygwin64\\bin\\bash.exe"

Of course you will need to set proper folder where your cygwin is installed on your machine.

Change size of fonts and window appearance

    "editor.fontSize": 14,
    "window.zoomLevel": 2,
GIT integration enable/disable

If you do not won’t GIT integration (doesn’t work good with cygwin git version on windows for example) use :

"git.enabled": false,

But, you will need to type git commands manually if you do that.

Angular v4 Typescript Snippets

The Visual Studio Code plugin for code snippets from JohnPapa.

Ctrl+P => ext install Angular2

Angular Language Service

The Visual Studio Code plugin for Html editor intellisense from Angular team.

Ctrl+P => ext install ng-template

 GIT History

Plugin for GIT file compare and history/log checking.

Install :

Ctrl+P => ext install githistory

Use:

Ctrl+Shift+P => search for "Git History"