TypeScript and Jasmine playground

The main purpose of this post is to create (with the less number of steps and understanding the whole process) a simple project where we can develop with TypeScript in Node, have tests written with Jasmine, and get code coverage with Istanbul.

First of all, you should check what node and npm versions you have installed in your system.

It’s not a great step, but at least, you will confirm that node and npm are installed and working properly.

node --version
npm --version

Maybe, it could be important for you to start with a clean environment (for me it’s always a good starting point). If it’s also your case, you can list your globally installed packages and, if you want, uninstall all with only one command.

npm list -g --depth=0

# Seek and destroy, be carefully
npm list -gp --depth=0 | 
Where-Object { $_ -notlike '*npm' } | 
ForEach-Object { 
	$package = ($_.SubString($_.IndexOf('\node_modules\') + '\node_modules\'.Length)).Replace('\', '/ )
	Write-Host $package
	npm rm -g $package
}

Now, you must create a directory and install all the dependencies in it. In this way, we will not pollute the global installation.

mkdir ts-example
cd .\ts-example\

For easing the transpilation process of TypeScript, we will use ts-node.

npm init --yes  # It will create package.json file
npm install --save-dev typescript ts-node @types/node

Although we could create the tsconfig.json file with npx tsc --init, in our case we will create it manually with the following content:

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2020",
    "types": ["node"],
    "esModuleInterop": true,
    "verbatimModuleSyntax": false
  }
}

To test TypeScript installation, we will use the following three files:

// main.ts
import { User } from "./user";  // A TypeScript interface
import path from 'path';  // A Node.js built-in module

const user: User = {
    name: "Sergio",
    age: 45
};
console.log(user);
const aPath = path.join("a", "b");
console.log(aPath);
// user.ts
export interface User {
    name: string;
    age: number;
}
// foo.ts
export default function foo(bar: string): string {
    if (bar == "bar") {
        return "bar";
    } else {
        return "baz";
    }
}

When you have created these 3 files at the root of the project, you should test ts-node with this command npx ts-node .\main.ts

I’m assuming that you are using VSCode, so we will create a configuration to launch or debug the current .ts file without the need to execute the previous command. For this, you must create the file .vscode\launch.json with the following content.

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Current TS File",
            "type": "node",
            "request": "launch",
            "args": [
                "${relativeFile}"
            ],
            "runtimeArgs": [
                "--nolazy",
                "-r",
                "ts-node/register"
            ],
            "sourceMaps": true,
            "cwd": "${workspaceRoot}"
        }
    ]
}

At this moment, you will be able to execute the current .ts file with Ctrl+F5 or even debug it with F5.

Our next step will be to install a typescript linter. We are humans and I’m sure that we will do some mistakes, we need a copilot. It seems that the de-facto standard for this subject is to use eslint.

npm install --save-dev eslint
npx eslint --init # It will create .eslintrc.json file

If you want to test the linter, a little change in main.ts will be enough to see it in action.

// main.ts
var user: User = { // var instead of const, not so good
    name: "Sergio",
    age: 45
};

Now, npx eslint . will advise you that var is evil.

At this point, we have a functional environment to play with TypeScript. For example, if we are interested in RxJS, we could do the following:

npm install rxjs
//hello_rxjs.ts
import { of } from "rxjs";

const observable$ = of(1, 2, 3, 4, 5)

observable$.subscribe({
    next: (item: number) => {
        console.log(item);
    },
    complete: () => {
        console.log("Completed");
    }
});

It’s true that if what you want is to test only RxJS, perhaps you would be more interested in using directly https://stackblitz.com/

In this last part of the project bootstrapping, we will install jasmine test framework and instanbul for the code coverage.

First it’s the turn of Jasmine.

npm install --save-dev jasmine @types/jasmine
npx jasmine init  # It will create spec\support\jasmine.mjs file

Also, we have to change some configurations in jasmine.mjs.

  spec_files: [
    "**/*[sS]pec.ts"
  ],
  helpers: [
    "helpers/**/*.ts"
  ],

Add this script in package.json.

"test": "ts-node .\\node_modules\\jasmine\\bin\\jasmine"

And for last, tsconfig.json should be modified like this:

{
  "extends": "ts-node/node12/tsconfig.json",
  "ts-node": {
    "transpileOnly": true,
    "files": true,
  }, 
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2020",
    "types": ["node", "jasmine"],
    "esModuleInterop": true,
    "verbatimModuleSyntax": false
  }
}  

With this very basic test case example, we can confirm that jasmine is working right.

// spec\main.spec.ts
import { User } from "../user";
import foo from "../foo";

describe("A suite", function () {
    it("contains spec with an expectation regarding with user", function () {
        const user: User = {
            name: "Sergio",
            age: 45
        };
        expect(user.age).toBe(45);
    });
    it("should execute foo", function () {       
        expect(foo("bar")).toBe("bar");
    });
});
npm test

If you want to use a more powerful assertion library, chai is a good choice.

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

And with only a few bit changes, we will have improved our assertions.

import { User } from "../user";
import { expect } from "chai";  // import expect from chai
import foo from "../foo";

describe("A suite", function () {
    it("contains spec with an expectation regarding with user", function () {
        const user: User = {
            name: "Sergio",
            age: 45
        };
        expect(user.age).to.be.eq(45);  // chai asssertion
    });
    it("should execute foo", function () {       
        expect(foo("bar")).to.be.eq("bar");  // chai asssertion
    });
});

Finally, we need to set up code coverage to achieve our initial goal.

npm install --save-dev nyc

By hand, create .nycrc file and include this content:

{
    "exclude": [
        "spec/**/*.ts"
    ],
    "include": [
        "**/*.ts"
    ],
    "reporter": [
        "html",
        "text",
        "text-summary"
    ]
}

If you want to measure code coverage not only in files touched by tests, you can do it adding "all": true to this file.

Anyway, add this script to package.json and run it with npm run coverage.

"coverage": "nyc npm run test"

And this is all. We have created a fully functional playground to execute TypeScript in an easy way, test with Jasmine, and get code coverage with Istanbul.

Bye!