Change from Jasmine, to Mocha/Chai/Sinon


#1

Would anyone have an example of how to convert from using Jasmine, to using Mocha using the new(est) CLI?


#2

I should add, I am attempting to use Alameda as well.


#3

The question is whether you need the bundler at all for your tests. Essentially you want to test the loose files anyways and as long as you’re not relying on lazy loaded stuff in your tests just ditch the loader at all.

So that said essentially all that you’d need is mocha + chai and perhaps ts-node if it’s a typescript based project. There shouldn’t be any specific for Aurelia needed in this case.

Here’s a link for the later (https://journal.artfuldev.com/write-tests-for-typescript-projects-with-mocha-and-chai-in-typescript-86e053bdb2b6).


#4

So I’ve tried it out now by myself here is what you should do for a fresh new scaffold using Alameda + TS

  1. run npm install --save-dev ts-node mocha chai @types/mocha @types/chai
  2. create a file tsconfig.test.json which is a clone of tsconfig.json in the project root and change
    target to es6 and module to commonjs
  3. create a file test/mocha.opts with contents
--require ./test/tshook
--timeout 6000
--slow 1500
--watch-extensions ts
./test/unit/**/*.spec.ts

with the opts file you can configure whatever options you’d like to have when starting up the test runner. Here’s a documentation for the available options

  1. create a file test/tshook.js (note the js ending) with contents
require("ts-node").register({
  project: "tsconfig.test.json",
});

I use this workaround via the require hook to preconfigure ts-node in order to do the transpilation on-the-fly.

  1. change the default app.spec.ts to use chai
import {App} from '../../src/app';
import { expect } from "chai";

describe('the app', () => {
  it('says hello', () => {
    expect(new App().message).to.equal('Hello World!');
  });
});
  1. add a new script into your package json:
"scripts": {
    "test": "mocha --opts test/mocha.opts"
  },
  1. start tests via npm run test for a single run
  2. if you want to run in watch mode start npm run test -- --watch

#5

if you prefer to handle that via au test instead you should modify aurelia_project/tasks/test.ts, add mocha-gulp and make it use that instead. I’ll leave that up to you if you prefer this way, since you want to customize your options as well.

one more thing, if you prefer the jasmine syntax over chai, which I do, perhaps try out https://www.npmjs.com/package/chai-jasmine


#6

Hi @zewa666,

Thank you for taking the time to work that up. I have been digging into the tools more, but still don’t understand much and appreciate it when those that do know, at least more then I do, show ways of doing things.

I do try to look up these things in search before asking, but it always seems I can only find things 2 or 3 years old and web years is even worst then dog years when it comes to being accurate.

I’ll try that out.


#7

Well in this specific case, experience is the amount of issues you went through previously :joy:

Let me know whether you need more help with anything specific. I didn’t include Sinon as it’s really pretty much unrelated to the whole setup. Just make sure to npm install --save-dev sinon and @types/sinon and you’re good to go


#8

Unfortunately that seems to be the case with a lot of things web related.

Another thing I didn’t mention was using wallaby as well. Seems my missing code there was the import for chai in the spec file. I was trying to use wallaby’s setup function as they show, and was only getting an undefined error whenever I tried assigning it to a window global. I added that import to the spec file and its now working. I need to see if I can move that up a level or three as I don’t want to have to add it to every test file. Same for Sinon.


#9

well for VanillaJS you could go the route with the tshook.js file and modify it like this

require("ts-node").register({
  project: "tsconfig.test.json",
});

global.expect = require("chai").expect;

trouble is with Typescript though that it will complain since there is no declared global expect variable. You’d likely have to write custom typings extending the global and adding your expect var.

Additionally make sure to uninstall any reference to jasmine, otherwise it’s typings will infer with your custom typings/mocha typings


also instead of assigning everything manually you could extend the whole global object with all features from Chai at once

Object.assign(global, require("chai"));

not sure if that is a good idea though, since you might overwrite other globals.


#10

I just tried something without the hook call, and it didn’t work.

Inside of wallaby added at the top:

global.expect = require("chai").expect;

But ‘expect’ is not being found in app.spec.ts as it appears its a different context.

So I moved it into the wallaby setup function that gets called each test.
I have included the full wallaby config, its at the bottom:

module.exports = function (wallaby)
{
    return {

        debug: true,

        files: [
            { pattern: 'node_modules/chai/chai.js', instrument: false, load: true },
            { pattern: 'node_modules/sinon/pkg/sinon.js', instrument: false, load: true },
            { pattern: 'tsconfig.json' },
            { pattern: 'src/**/*.ts' },
            { pattern: 'src/**/*.html' },
            { pattern: 'test/**/*.ts' },
            { pattern: '!test/unit/**/*.spec.ts' }
        ],

        tests: ['test/unit/**/*.spec.ts'],

        env: {
            type: 'node',
            runner: 'node'
        },

        testFramework: 'mocha',

        compilers: {
            '**/*.ts': wallaby.compilers.typeScript({
                module: 'commonjs'
            })
        },

        setup: function (wallaby)
        {
            global.expect = require("chai").expect;
        }
    };
};

After removing the import in app.spec.ts the test now runs correctly, however TS is complaining that the ‘to’ function does not exist on the ‘expect’ object

I am much further along then what I was at least! :slight_smile:

(ps: I did add the typings already)


#11

I think I got it. Hope that I haven’t overlooked anything but try the following:

  1. in the folder custom_typings add a subfolder named globals containing a file index.d.ts with contents:
declare const expect: Chai.ExpectStatic;
// repeat for all others needed
  1. open up tsconfig.test.jsonand add the following two compilerOptions
"types": [ "node", "mocha", "chai", "globals" ],
"typeRoots": ["node_modules/@types", "custom_typings"]

now it should work in TS-Node and also make the VSCode warnings disappear.


#12

@zewa666 WOW!

So I still haven’t completely done your first suggestion so that ‘au test’ will run under mocha.

I am running wallaby for live coding though, and I just did the noted update and works like a champ setting the value once in wallaby’s setup function, and not having to import into every spec.ts file.

So what I did was:
I updated the standard tsconfig.json with those changes, added the new type definition folder/file, updated setup to init ‘global.expect’, and removed the import from the spec.ts file and it removed all the tslint/compile errors.

Thank you so much for chipping away at that and getting it to work. Awesome!


#13

So a pattern question attached to this and what I am doing. (this is all prototyping/experimenting right now)

Generally I try to stay away from globals as they can cause long term issues and maintainability problems. In this case for instance, I am setting the default assertion library to chai, really just to save a little bit of boiler plate in each test having to add in the import, but is it worth it long term?

So lets say, yeah, we are pretty set on using chai for the project, but a few months, a year, or more there is another assertion framework that would ease some test-ability issues. Obviously, we cannot, nor would we want to change the now ‘global’ chai library, but we would import and add that new library to the files as needed. However, now you are mixing different assertion types, where one is clearly defined in the test file, but the other you now have to ‘know’ is the global default. I would imagine it could become confusing to new developers to the project, and you would end up perhaps with imports being added when they shouldn’t and not added when they should.

So, with that said, I am leaning toward having a little extra boiler plate code (snippets to ease coding tasks?), but long term readability and maintainability by having everything right there in the file to see exactly what is being used (where possible.)

For those that have used unit testing long term have you run into issues like these?
Any comments, or thoughts?


#14

I’ve just ran into exact same situation when moving from Jasmine/Karma to Jest on my current project. Finding every spyOn, which if not preceded with jest. would point to the wrong jasmine global, and no I couldn’t get rid of jasmine right away since it was a step by step port, was a major PITA. Same applies to expect calls which would have the wrong reference to Jasmine etc.

Sadly I think we didn’t really improve too much with the port to Jest, in regards to easier future upgrades. But I think the necessity to import explicitly from chai is one of it’s better features as it will definitely help you if you ever plan to migrate away from Mocha/Chai. Same applies to Sinon.

In general, I feel people are often too used to how it used to be with globals and just stick with that mindset. Let’s be honest, with any decent IDE nowadays pulling in the missing import statements is merely a matter of one or two shortcuts. My avg. APM in Starcraft have been around 170 as a hobby player, and an avg CPM of 460 (https://typing-speed-test.aoeu.eu/) so given that it costs me around 0.5 - 1 seconds which is absolutely affordable for the long-term easier refactoring opportunity :wink:


#15

At this point, thinking it through, it just causes to many long term issues to try and hide that in my opinion, and apparently @zewa666 has ran into those same issues in real world projects.

What is the saying? Have everything as near to where its used as possible, and get rid of it as soon as possible…

Sometimes it makes sense, or has to be done a certain way, I get it, but when you do document document document. In this case, its not needed.