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

To test TypeScript installation, we will use a very basic example that it should be enough.

// 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}",
            "protocol": "inspector",
        }
    ]
}

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

The execution of npx eslint --init will ask you several questions. I have answered with the following choices:

  • How would you like to use ESLint?
    • To check syntax and find problems
  • What type of modules does your project use?
    • JavaScript modules (import/export)
  • Which framework does your project use?
    • None of these
  • Does your project use TypeScript?
    • Yes
  • Where does your code run?
    • Node
  • What format do you want your config file to be in?
    • JSON
  • Would you like to install them now with npm?
    • Yes

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

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

Now, both npx eslint . in the command line and VSCode using the official extension for VSCode will advise you that var is evil.

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.json file
npx tsc --init  # It will create tsconfig.json file

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

  "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, this ts-node related content at the beginning of the tsconfig.json file.

{
  // This is an alias to @tsconfig/node12: https://github.com/tsconfig/bases
  "extends": "ts-node/node12/tsconfig.json",
  // Most ts-node options can be specified here using their programmatic names.
  "ts-node": {
    // It is faster to skip typechecking.
    // Remove if you want ts-node to do typechecking.
    "transpileOnly": true,
    "files": true,
    "compilerOptions": {
      // compilerOptions specified here will override those declared below,
      // but *only* in ts-node.  Useful if you want ts-node and tsc to use
      // different options with a single tsconfig.json.
    }
  },  

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"

By last, I would like to tell you about two VSCode extensions that could ease your workflow. The first one, Jasmine Test Explorer, is a test-runner for executing and debugging a test from inside VSCode (including test output in Jasmine Tests output panel). Very helpful, it’s a must-have for me. The second one, Test Focus Highlighter, will inform you with a indicator in the glutter, when you use fdescribe or fit for focusing a suite or spec.

Thanks to @Sary, because I was making a mistake configuring the first extension and I couldn’t see why it was failing. You should add to your VSCode settings the following line "jasmineExplorer.nodeArgv": ["-r", "ts-node/register"] (a key with two items). Instead of that, I was using the UI (looking for Jasmine settings) and the final content that I was getting was "[\"-r\", \"ts-node/register\"]" (one key and one item). Oh, my god, I was so wrong.

At the end, you should have something like this:

settings

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!