• Stars
    star
    160
  • Rank 234,703 (Top 5 %)
  • Language
    TypeScript
  • Created almost 7 years ago
  • Updated about 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A sample repo leveraging TypeScript, jest and ts-jest (with code coverage and debugging)

Sample Project using ts-jest

The goal in this project is to create a TypeScript project that can do all of the following:

  • Compile code as an es5 library that can be published as a Node module with typings.
  • Using jest and ts-jest for testing
  • Provides proper stack traces for failed tests
  • Provide accurate code coverage metrics
  • Can be debugged using the Node debugger with proper source maps

Basic Setup

Module and Dependencies

I start by initialing this as an npm project.

$ yarn init .

Then, I install typescript, jest, ts-jest and @types/jest as dependencies:

$ yarn add -D typescript jest ts-jest @types/jest

At the time of this writing, that means [email protected], [email protected] and [email protected].

TypeScript

Next, we initialize this as a TypeScript project using:

$ npx tsc --init .

I want my TypeScript generated code to be stored in ./lib and I want declarations generated. So, I configure outDir in tsconfig.json to be ./lib.

Files

My .gitignore is then configured to be:

/node_modules
/lib

...while my .npmignore is just:

/node_modules

For the same reason, I remove the default value for files in tsconfig.json and replace it with:

    "exclude": ["node_modules", "lib"]

Source

To start, I create a src/index.ts that contains a simple function:

export function sampleFunction(x: string): string {
    return x + x;
}

I also add a simple jest test. I prefer to keep my tests in a completely separate location, so I'll put all my tests in __tests__. So I create the following test case in __tests__/base.spec.ts:

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

describe("This is a simple test", () => {
    test("Check the sampleFunction function", () => {
        expect(sampleFunction("hello")).toEqual("hellohello");
    });
});

Configurating Jest

At this point, I'd like to run that test. But first I need to create a jest.config.js file for all my jest settings. This has to take into account the fact that I'm using ts-jest and the fact that my tests are stored in __tests__. So the resulting file looks like this:

module.exports = {
    transform: {
        "^.+\\.tsx?$": "ts-jest",
    },
    testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
    moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
};

Scripts

I then add the following scripts to package.json:

  "scripts": {
    "compile": "tsc",
    "test": "jest"
  }

At this point, if I run yarn test, I get exactly what I was hoping for:

 PASS  __tests__/base.spec.ts
  This is a simple test
    โœ“ Check the sampleFunction function (3ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Code Coverage

Configuration

To enable code coverage, I update my jest.config.js file to:

module.exports = {
    transform: {
        "^.+\\.tsx?$": "ts-jest",
    },
    testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
    moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
    collectCoverage: true,
};

I'll also want to update my .gitignore and .npmignore files to avoid version controlling or publishing the coverage directory generated by jest.

Code Organization

At this point, I'm going to start introducing sub-modules in my project. So I'll add a src/core and a src/utils module just so make things sligtly more realistic. Then I'll export the contents of both of these so that src/index.ts looks like this:

export * from "./core";
export * from "./utils";

These then import specific files containing various types and functions. Initially, I'll create a very simple set of types for representing extremely simple expressions with only literals and the binary operations +, -, * and /. Then I can write a few tests like these:

import { evaluate, Expression } from "../src";

describe("Simple expression tests", () => {
    test("Check literal value", () => {
        expect(evaluate({ type: "literal", value: 5 })).toBeCloseTo(5);
    });
    test("Check addition", () => {
        let expr: Expression = {
            type: "binary",
            operator: "+",
            left: {
                type: "literal",
                value: 5,
            },
            right: {
                type: "literal",
                value: 10,
            },
        };
        expect(evaluate(expr)).toBeCloseTo(15);
    });
});

So far so good. But note that if I actually run these tests, I get these results:

 PASS  __tests__/base.spec.ts
  Simple expression tests
    โœ“ Check literal value (4ms)
    โœ“ Check addition

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.048s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files      |    66.67 |     37.5 |       50 |    66.67 |                |
 src           |      100 |      100 |      100 |      100 |                |
  index.ts     |      100 |      100 |      100 |      100 |                |
 src/core      |    61.54 |     37.5 |      100 |    61.54 |                |
  functions.ts |    54.55 |     37.5 |      100 |    54.55 | 14,16,18,20,25 |
  index.ts     |      100 |      100 |      100 |      100 |                |
 src/utils     |    66.67 |      100 |        0 |    66.67 |                |
  checks.ts    |       50 |      100 |        0 |       50 |              2 |
  index.ts     |      100 |      100 |      100 |      100 |                |
---------------|----------|----------|----------|----------|----------------|

Note the lack of code coverage. Adding a few more test cases along with some /* istanbul ignore ... */ comments to let istanbul know what it can safely ignore, we get to:

 PASS  __tests__/base.spec.ts
  Simple expression tests
    โœ“ Check literal value (3ms)
    โœ“ Check addition
    โœ“ Check subtraction
    โœ“ Check multiplication (1ms)
    โœ“ Check division

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.353s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files      |      100 |      100 |      100 |      100 |                |
 src           |      100 |      100 |      100 |      100 |                |
  index.ts     |      100 |      100 |      100 |      100 |                |
 src/core      |      100 |      100 |      100 |      100 |                |
  functions.ts |      100 |      100 |      100 |      100 |                |
  index.ts     |      100 |      100 |      100 |      100 |                |
 src/utils     |      100 |      100 |      100 |      100 |                |
  checks.ts    |      100 |      100 |      100 |      100 |                |
  index.ts     |      100 |      100 |      100 |      100 |                |
---------------|----------|----------|----------|----------|----------------|

Now, if we change a test to make it fail, we get something like this:

โ— Simple expression tests โ€บ Check division

    expect(received).toBeCloseTo(expected, precision)

    Expected value to be close to (with 2-digit precision):
      1
    Received:
      2

      19 |     test("Check division", () => {
      20 |         let expr = bin("/", 10, 5);
    > 21 |         expect(evaluate(expr)).toBeCloseTo(1);
      22 |     });
      23 | });
      24 |

      at Object.<anonymous> (__tests__/base.spec.ts:21:32)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 4 passed, 5 total
Snapshots:   0 total
Time:        1.535s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files      |      100 |      100 |      100 |      100 |                |
 src           |      100 |      100 |      100 |      100 |                |
  index.ts     |      100 |      100 |      100 |      100 |                |
 src/core      |      100 |      100 |      100 |      100 |                |
  functions.ts |      100 |      100 |      100 |      100 |                |
  index.ts     |      100 |      100 |      100 |      100 |                |
 src/utils     |      100 |      100 |      100 |      100 |                |
  checks.ts    |      100 |      100 |      100 |      100 |                |
  index.ts     |      100 |      100 |      100 |      100 |                |
---------------|----------|----------|----------|----------|----------------|

Note that the stack track is correct. It points to the problem in the TypeScript code.

Compilation

Recall that we added a compile script to our package.json. We can compile the code with yarn compile. Doing so, we see that the lib directory is populated with two subdirectories, src and __tests__.

However, if we look in those directories, we will find that they only include the generated Javascript code. They do not include type definitions. In order to generate type definitions (.d.ts files) so that other TypeScript users can benefit from all the type information we've added to our code, we have to set the declaration field in our tsconfig.json file to be true.

Also note that in order for others to use this package as an NPM module, you need to set the main field in package.json to lib/src/index.js. Furthermore, in order for others to be able to access the types in this module, we also need to set the typings field in package.json to lib/src/index.d.ts. In other words,

    "main": "lib/src/index.js",
    "typings": "lib/src/index.d.ts",

If properly configured, we can then launch a node session and import our new package:

$ node
> var me = require(".")
undefined
> me
{ evaluate: [Function: evaluate],
  assertNever: [Function: assertNever] }
>

Now be sure to update your jest.config.js to include the following setting or jest will start matching the code in the lib/__tests__ directory:

    testPathIgnorePatterns: ["/lib/", "/node_modules/"],

Debugging

Finally, we come to debugging. I'm using Visual Studio Code, so I'll demonstrate how to get debugging working there. Some of this information may very well translate to other IDEs.

In VSCode, we can go to the debugging sidebar. Initially, next to the "play" button will be the words "No Configuration". Clicking on that brings up a pull-down menu with an option "Add Confiuration...".

As much as I love TypeScript, debugging is really its Achilles Heel. It isn't that you cannot debug, it is that it is just difficult to get working. If you select "Add Configuration..." and then "Node.js", you'll see several preconfigurations including one for mocha. But there isn't one for jest. So you'll have to create your own .vscode/launch.json file. Fortunately, the jest page suggestions you create a .vscode/launch.json file that looks like this:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug Jest Tests",
            "type": "node",
            "request": "launch",
            "runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/.bin/jest", "--runInBand"],
            "console": "integratedTerminal",
            "internalConsoleOptions": "neverOpen"
        }
    ]
}

I was pleasantly surprised to find that I could not only run my tests and get code coverage as usual, but also set breakpoints in both the tests (i.e., in __tests__/base.spec.ts) as well as in the code (e.g., src/core/functions.ts) and the debugger will find them.

Note that I tested all this on Node 8.x. I've seen issues with debugging using Node 6.x so if you are having trouble there, you might consider upgrading (or let, if you manage to fix it, submit a PR for this README explaining the fix).

More Repositories

1

ModelicaBook

Source for my new Modelica Book
JavaScript
95
star
2

typescript-storyshots

Sample project using StoryShots, Jest and TypeScript
TypeScript
54
star
3

fmusdk

This is an SDK (originally developed by QTronic) for testing the new Functional Mockup Interface developed by the Modelisar project.
C
11
star
4

urns

A URN encoding and decoding library for TypeScript and Javascript
TypeScript
9
star
5

ModelicaWebRef

A Quick Reference sheet for Modelica
HTML
8
star
6

recon

Web and network friendly simulation data formats
Python
8
star
7

modelica-tree-sitter

A tree-sitter grammar capable of parsing Modelica
JavaScript
5
star
8

FirstBookExamples

Examples from my first book on Modelica, "Introduction to Physical Modeling with Modelica"
HTML
5
star
9

book-generator

A react-static based site generator for Modelica by Example
TypeScript
5
star
10

OpenCollaboration

Public ORNL advanced reactor SMR models available for open collaboration
C
4
star
11

mat-parser

Code to parse both MATLAB v4 files and Dymola results files
TypeScript
3
star
12

siren-types

A TypeScript library for representing Siren entities
TypeScript
3
star
13

HarmonicMotion

Demonstrating an interesting experiment in harmonic motion
Modelica
3
star
14

modelica-newsletter

Some code to help push out the Modelica Newsletter
JavaScript
2
star
15

LotkaVolterra

Exploration of the Lotka-Volterra equations along with component representations.
2
star
16

go-siren

An implementation of Siren related data structures in Golang
Go
2
star
17

MythPCH

A web application to serve MythTV content to an Networked Media Tank (Popcorn Hour)
Python
2
star
18

EconomicsLibrary

A very basic Modelica library of economic models
Modelica
2
star
19

lessonplan

A tool for building lesson plans for my Modelica Playground at playground.modelica.university
Go
1
star
20

siren-nav

A library for navigating Siren APIs
TypeScript
1
star
21

book-nextgen

A new book generator based on next.js
TypeScript
1
star
22

book-apps

A repository with code for React apps that request and display simulation results from Modelica by Example
TypeScript
1
star