13

I'm using Angular 4, Webpack 2.4.1, Karma 1.6 and Jasmine 2.6.1 and am writing ES2015 not TypeScript

I've got a tiny angular demo app and I want to add unit tests. The demo app itself itself is working and Webpack is bundling everything correctly, but when I try to run the unit tests I see some errors in the console like this:

ReferenceError: Can't find variable: Map

at Static/js/app.welcome.js:2569

(app.welcome.js is the name of my component)

Webpack appears to be building my test bundle properly, Karma server is starting up correctly and PhantomJS is launching correctly, but then I see several of the Map errors.

I'm definitely not using the Map() constructor in my own code.

Here are my files -

app.welcome.js:

import {Component} from '@angular/core';

class WelcomeComponent {
    constructor () {
        this.welcomeMessage = 'Welcome to Angular 4';
    }
}

WelcomeComponent.annotations = [
    new Component({
        selector: 'my-app',
        template: '<h1>{{welcomeMessage}}</h1>'
    })
];

export {WelcomeComponent};

app.welcome.spec.js:

import {TestBed} from '@angular/core/testing';
import {WelcomeComponent} from '../../js/app.welcome';

describe('The Welcome component', function () {

    let component;

    beforeEach(function() {
        TestBed.configureTestingModule({
            declarations: [WelcomeComponent]
        });

        const fixture = TestBed.createComponent(WelcomeComponent);
        component = fixture.componentInstance;
    });

    it('should be a component', function() {
        expect(component).toBeDefined();
    });

    it('should have a welcome message', function () {
        expect(component.welcomeMessage).toEqual('Welcome to Angular 4');
    });

});

karma.conf.js:

const webpack = require('webpack');

module.exports = function(config) {
    config.set({
        basePath: '',
        frameworks: ['jasmine'],
        files: [
            './Static/js/**/*.js',
            './Static/test/**/*.spec.js'
         ],
         exclude: [
             './Static/js/main.js'
         ],
         preprocessors: {
             './Static/js/**/*.js': ['webpack'],
             './Static/test/**/*.spec.js': ['webpack']
         },
         reporters: ['progress'],
         port: 9876,
         colors: true,
         logLevel: config.LOG_INFO,
         autoWatch: false,
         browsers: ['PhantomJS'],
         singleRun: true,
         concurrency: Infinity,
         webpack: {
             module: {
                 rules: [{
                     test: /\.js$/,
                     use: [{
                         loader: 'babel-loader',
                         options: { presets: ['es2015'] }
                     }]
                 }]
             },
             plugins: [
                 new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, './src')
             ]
         }
     })
}

I've tried adding imports to my test file like import 'zone.js'; and import 'core-js/es6'; after reading other answers here, but this has not helped.

I've looked through Testing -ts - GUIDE and I don't appear to be missing anything huge from the earlier basic examples, but the problem is that all the official docs are geared towards TypeScript, while I want to use ES2015.

I understand that Map is an new type of object in ES2015 and not a variable as indicated by the error. Shouldn't Babel support this though?

Can anyone help?

Community
  • 1
  • 1
danwellman
  • 9,068
  • 8
  • 60
  • 88

2 Answers2

7

This error is thrown because there's no Map in browsers. PhantomJS is used as Karma driver, and it doesn't support ES6 features.

If polyfills (e.g. core-js) aren't loaded in files that were included in tests, they should be loaded separately, for example via karma-es6-shim plugin:

    ...
    frameworks: ['es6-shim', 'jasmine'],
    ...
danwellman
  • 9,068
  • 8
  • 60
  • 88
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    Thanks, I tried this, but the message just changed to `ReferenceError: Can't find variable: WeakMap` as es6-shim doesn't support WeakMap. Most browsers do support map now, incredible even IE11 has basic support according to Kangax! But also, shouldn't Babel via Webpack provide support for these things? – danwellman May 05 '17 at 13:56
  • 1
    No, Babel shouldn't. There's [Babel polyfill](https://babeljs.io/docs/usage/polyfill/), and it is supposed to be loaded manually. See https://github.com/babel/karma-babel-preprocessor#polyfill . Yes, PhantomJS is simple, but nobody forces you to use it with Karma. You can use Chrome driver instead if you want. See https://github.com/paulmillr/es6-shim#weakmap-shim on why it's not included in es6-shim. – Estus Flask May 05 '17 at 14:07
  • Well, the whole purpose of the Babel Webpack plugin is to transpile ES2015 -> ES5 as part of bundling. But I will try Chrome browser instead of PhantomJS. I hear Chrome headless is the new hotness :) – danwellman May 05 '17 at 14:28
  • 1
    Indeed, it transpiles all code that can't be polyfilled. And it should be noticed that some things cannot be polyfilled at all (like Proxy). No one would be happy if Babel applied all polyfills by force, this would create much more issues than it would solve. I haven't tried headless Chrome yet, but headed Chrome has been doing the trick for quite long time. – Estus Flask May 05 '17 at 14:34
  • I've tried this a few different ways, but now I'm getting one nondeterministic unit test failure per run. Are there ordering or timing dependencies with this? The exact message I'm seeing is: `ReferenceError: Can't find variable: Map thrown` – ehdv Feb 28 '18 at 21:11
  • 1
    Vanilla-er `Map` shim over [here](https://stackoverflow.com/a/49438745/1028230). YMMV on implementation details, but it's a simple include and forget, no npm middlehuman. – ruffin Jun 17 '22 at 21:12
4

This is what has worked for me: I was using PhantonJs with karma and nightwatch for my testing . I was getting this error during JhantomJs initialization and as test were getting started because because there's no Map in browsers. PhantomJS is used as Karma driver, and it doesn't support ES6 features.

I tried to load polyfills but that did not help as well. Then I saw that support for PhantonJs has long gone and felt it wont work. So I took an effort to replace PhantomJs with Chrome headless browser and surprisingly it was pretty easy to replace.

Here are the thing I did to make the change from PhantomJs to Chrome headless:

  1. Package.json - puppeteer, chromedriver, karma-chrome-launcher as dependency
  2. Add the following in karma.conf.js
const puppeteer = require('puppeteer');
process.env.CHROME_BIN = puppeteer.executablePath();

module.exports = function(config) {
  config.set({ 
    .
    browsers: ['ChromeHeadless'],
    .
   });

  1. Add/Edit the following in nightwatch.json
    "chrome": {
      "desiredCapabilities": {
        "browserName": "chrome",
        "javascriptEnabled": true,
        "acceptSslCerts": true,
        "chromeOptions": {
          "args": ["--no-sandbox", "headless"]
        }
      }

This is something which has worked for me; may or may not work for you depending on the kind of side things you are using with JhantomJs for testing. Nevertheless should helpful for anyone who is trying to move away PhantomJs.

ajain
  • 444
  • 4
  • 7