Unit testing using Jasmine and TypeScript

JavascriptUnit TestingTypescriptJasmineChutzpah

Javascript Problem Overview


I am trying to get a unit test written in Typescript using Jasmine to compile. With the following in my unit-test file, Resharper prompts me with a link to import types from jasmine.d.ts.

/// <reference path="sut.ts" />
/// <reference path="../../../scripts/typings/jasmine/jasmine.d.ts" />

describe("Person FullName", function () {
    var person;

    BeforeEach(function () {
        person = new Person();
        person.setFirstName("Joe");
        person.setLastName("Smith");
    });

    It("should concatenate first and last names", function () {
        Expect(person.getFullName()).toBe("Joe, Smith");
    });
});

So I click on the link and end up with the following (actually resharper only prefixed the describe function with "Jasmine.", so I manually prefixed the other Jasmine calls):

/// <reference path="sut.ts" />
/// <reference path="../../../scripts/typings/jasmine/jasmine.d.ts" />
import Jasmine = require("../../../Scripts/typings/jasmine/jasmine");

Jasmine.describe("Person FullName", function () {
    var person;

    Jasmine.BeforeEach(function () {
        person = new Person();
        person.setFirstName("Joe");
        person.setLastName("Smith");
    });

    Jasmine.It("should concatenate first and last names", function () {
        Jasmine.Expect(person.getFullName()).toBe("Joe, Smith");
    });
});

However the import statement has a red squiggly line with error message "Unable to resolve external module ../../../scripts/typings/jasmine/jasmine. Module cannot be aliased to a non-module type"

Any idea what is causing this error? I've checked that the "Module System" option is set to AMD in my project build settings. I've also checked that the jasmine module is defined in jasmine.d.ts. I downloaded this file from DefinitelyTyped site.

declare module jasmine {
    ...
}

Javascript Solutions


Solution 1 - Javascript

Here's (in my opinion) the best way to test a ts-node app as of 2018:

npm install --save-dev typescript jasmine @types/jasmine ts-node

In package.json:

{
  "scripts": {
    "test": "ts-node node_modules/jasmine/bin/jasmine"
  }
}

In jasmine.json change file pattern to *.ts

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

In your spec files:

import "jasmine";
import something from "../src/something";

describe("something", () => {
    it("should work", () => {
        expect(something.works()).toBe(true);
    });
});

To run the tests:

npm test

This will use the locally installed versions of ts-node and jasmine. This is better than using globally installed versions, because with local versions, you can be sure that everyone is using the same version.

Note: if you have a web app instead of a node app, you should probably run your tests using Karma instead of the Jasmine CLI.

Solution 2 - Javascript

Put this at the top of your typescript spec file:

/// <reference path="../../node_modules/@types/jasmine/index.d.ts" />
let Jasmine = require('jasmine');

You must install the following Jasmine modules for that to work:

$ npm install jasmine-core jasmine @types/jasmine @ert78gb/jasmine-ts --save-dev

Once you do that, the IDE (such as WebStorm) will recognize Jasmine and its functions such as describe(), it(), and expect().. So you don't need to prefix them with "Jasmine." Also, you can run your spec files from the command line using the jasmine-ts module. Install these command line tools globally:

$ npm install -g jasmine @ert78gb/jasmine-ts

Then configure the "jasmine" command line module so that Jasmine can find its configuration file. Then you should be able to run jasmine-ts and your spec file should run fine from the command line:

./node_modules/.bin/jasmine-ts src/something.spec.ts

.. and, you can configure your IDE to run it like that as well, and debug runs that way should also work (works for me).

Writing your tests this way, you can run a Jasmine test spec on the server side without Karma, or run it in a web browser using Karma. Same typescript code.

Solution 3 - Javascript

If you have issues with imports, use tsconfig-paths

npm i ts-node tsconfig-paths types/jasmine jasmine --save-dev

Run typescript-enabled jasmine:

ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine.js

Ensure that your jasmine will search .ts files:

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

To test your scripts you may also need polyfills if you use them in your project. Create a helper file with required imports, like helpers/global/polifill.ts

import 'core-js';

Solution 4 - Javascript

For me I did the following:

Install Typings

npm install typings --global

Then add the typings in for jasmine

typings install dt~jasmine --save --global

Solution 5 - Javascript

You could try a side-effect only import which brings in the @types/jasmine declaration and places the jasmine functions into the global scope so you don't need to prefix each call with jasmine. allowing a quick port from existing unit tests and still plays nice with webpack.

// tslint:disable-next-line:no-import-side-effect
import "jasmine";

describe("My Unit Test", () => { /* ... */ } );

Of course you still need to install jasmine and the typings:

$ npm i jasmine @types/jasmine --save-dev

But no need for specialized jasmine loaders for ts or node. Just run jasmine against the compiled js files:

$ node ./node_modules/jasmine/bin/jasmine.js --config=test/support/jasmine.json

Assuming your typescript files are within a "test" subdirectory compiling to bin/test and you have a test/support/jasmine.json with something like this:

{
    "spec_dir": "bin/test",
    "spec_files": [
      "**/*[sS]pec.js"
    ],
    "stopSpecOnExpectationFailure": false,
    "random": false
}

P.S. all of the above works on Windows too

Solution 6 - Javascript

Include this to your jasmine html file,...

<script type="text/javascript" src="jasmine/lib/jasmine-2.0.0/jasmine.js"></script>

...or install the npm jasmine package:

npm install --save-dev jasmine

when you are using the second way (jasmine as module) you have to import it:

var jasmine = require('jasmine');

or

import jasmine from 'jasmine';

then change the other code:

jasmine.describe("Person FullName", function () {
	var person;

	jasmine.beforeEach(function () {
		person = new Person();
		person.setFirstName("Joe");
		person.setLastName("Smith");
	});

	jasmine.it("should concatenate first and last names", function () {
		jasmine.expect(person.getFullName()).toBe("Joe, Smith");
	});
});

Personally i would prefer the first way without using the jasmine npm module. (I didn't test the module yet)

Solution 7 - Javascript

You didn't ask for this, but for bonus points: once you get AJ's answer up and running (using ts-node to invoke the Jasmine startup script), you can add a new task:

"scripts": {
  "watch": "ts-node-dev --respawn -- ./node_modules/jasmine/bin/jasmine src/**.spec.ts"
}

Of course, you can pass your specs or any other arguments using Jasmine's config file instead, if you like. Now, Jasmine will run all your specs once, then ts-node-dev will sit in the background waiting for your tests or anything they might have require'd to change, at which point jasmine will be run again. I haven't worked out a way to only run the tests that have changed (or tests whose imports have changed) yet -- as far as I can tell, that's not supported anyway;

Solution 8 - Javascript

My folder structure

Spec folder is on the root of project

    spec
      \- dist              // compiled tests
      \- helpers           // files modified testing env
         \- ts-console.ts  // pretty prints of results
      \- support
         \- jasmine.json
      \- YourTestHere.spec.ts
      \- tsconfig.json     // tsconfig for your tests

Files content

ts-console.ts

const TSConsoleReporter = require("jasmine-console-reporter");
jasmine.getEnv().clearReporters();
jasmine.getEnv().addReporter(new TSConsoleReporter());

jasmine.json

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

With extra script in package.json

"scripts": {
  "test": "rm -rf ./spec/dist && tsc -p ./spec && jasmine"
}

and add line "/spec/dist" to .gitignore

Run your tests!

Run your tests with npm test.

How does it work?

  1. Directory for tests is cleaned.
  2. Tests are compiled to spec/dist folder to JS.
  3. Tests are runned from this location.

I hope it will help you. Good coding.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionaw1975View Question on Stackoverflow
Solution 1 - JavascriptAJ RichardsonView Answer on Stackoverflow
Solution 2 - JavascriptYavin5View Answer on Stackoverflow
Solution 3 - JavascriptSergey P. aka azureView Answer on Stackoverflow
Solution 4 - JavascriptAndrew NewlandView Answer on Stackoverflow
Solution 5 - JavascriptGraeme WickstedView Answer on Stackoverflow
Solution 6 - JavascriptmarcelView Answer on Stackoverflow
Solution 7 - JavascriptCodererView Answer on Stackoverflow
Solution 8 - JavascriptVladimír MlázovskýView Answer on Stackoverflow