Supported technologies: Angular, Angular CLI

Wallaby can be run with Angular CLI either with or without a configuration file. We recommend using Automatic configuration where no configuration file is required (supported from Angular CLI v8.2.0+). If you are using a version of Angular prior to v8.2.0, then you must configure Wallaby to use a configuration file.

Unlike using ng test, Wallaby can continuously run tests for your entire Angular CLI workspace at the same time, only running tests affected by your current changes.

For step-by-step instructions to configure and use Wallaby with an Angular CLI application, please refer to our Angular CLI tutorial.

If you’re interested in using Wallaby for Angular 1.x projects refer to our angular-legacy documentation.

Automatic Configuration Workspace Overrides

When using Automatic Configuration to run tests for the entire workspace, Wallaby creates merged virtual copies of test.ts, polyfills.ts and tsconfig.spec.json files. If these files have been modified then it is possible they cannot be merged. Wallaby supports the ability to explicitly define the merged contents of these files.

To explicitly override the merged contents of test.ts, polyfills.ts and tsconfig.spec.json, you must create an override file for each file name. For example, to replace the merged override of all test.ts files, you must create a file in the workspace root named test.wallaby.ts. The same is true for polyfills.ts and tsconfig.spec.json which become polyfills.wallaby.ts and tsconfig.wallaby.spec.json respectively.

Example merge override for: test.ts (test.wallaby.ts)

import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

// shared imports for all your projects
// imports for app-a, lib-b, lib-c, app-d etc.

// shared code for all your projects
// code for app-a, lib-b, lib-c, app-d etc.

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);

// shared code for all your projects
// code for app-a, lib-b, lib-c, app-d etc.

Example merge override for: polyfills.ts (polyfills.wallaby.ts)

import 'zone.js/dist/zone';
// shared code for all your projects
// code for app-a, lib-b, lib-c, app-d etc.

Example merge override for: tsconfig.spec.json (tsconfig.wallaby.spec.json)

{
  "extends": "./tsconfig.json",
    "compilerOptions": {
        "types": [
            "jasmine",
            "node"
        ],
        "paths": {
            "lib-b": [
                "projects/lib-b/src/public-api.ts"
            ],
            "lib-b/*": [
                "projects/lib-b/src/*"
            ],
            "lib-c": [
                "projects/lib-c/src/public-api.ts"
            ],
            "lib-c/*": [
                "projects/lib-c/src/*"
            ]
        }
    },
  "files": [
        "projects/app-a/src/test.ts",
        "projects/app-a/src/polyfills.ts",
        "projects/lib-b/src/test.ts",
        "projects/lib-b/src/polyfills.ts"
        "projects/lib-c/src/test.ts",
        "projects/lib-c/src/polyfills.ts",
        "projects/app-d/src/test.ts",
        "projects/app-d/src/polyfills.ts"
  ],
  "include": [
        "projects/app-a/src/**/*.spec.ts",
        "projects/app-a/src/**/*.d.ts",
        "projects/lib-b/src/**/*.spec.ts",
        "projects/lib-b/src/**/*.d.ts",
        "projects/lib-c/src/**/*.spec.ts",
        "projects/lib-c/src/**/*.d.ts",
        "projects/app-d/src/**/*.spec.ts",
        "projects/app-d/src/**/*.d.ts"
  ]
}

If you have created a polyfills.wallaby.ts override, then it must be added to your TypeScript files configuration section.

Automatic Configuration Project Overrides

When using Automatic Configuration to run tests for a specific project, Wallaby slightly modifies your project’s test.ts file. Wallaby has its own mechanism for resolving test files and removes the code that provides webpack with the list of tests to run. If this file has been modified then it is possible that Wallaby cannot safely find/delete this code then Angular CLI defaults for test.ts will be used. If desired, you can provide Wallaby with a differnet file to run by creating a test.wallaby.ts file in the same folder as your test.ts file.

Example override for: test.ts (test.wallaby.ts)

import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

// your imports

declare const require: any;

// your code

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);

// your code

// !  TEST FILE IMPORTS ARE RESOLVED BY WALLABY AND     !
// !  SHOULD BE COMMENTED OUT OR DELETED FROM THIS FILE !
//
// const context = require.context('./', true, /\.spec\.ts$/);
// context.keys().map(context);

Manual Configuration (Wallaby.js File)

Starting from Angular CLI v8.2.0 and higher manual configuration is not supported, please use automatic configuration. If you encounter problems using automatic configuration, please let us know.

If you are not using Automatic Configuration to run your Angular CLI project’s test, then you must provide a configuration file that tells Wallaby how to load and run your tests. We have created a configuration file that works for Angular CLI projects that use karma as the test runner.

Copy the configuration shown below and paste it into a file named wallaby.js in your project root. The configuration file below may seem foreign or scary but you don’t need to worry about what the configuration file is doing in order to use Wallaby.js with Angular.

You may read more about how to configure wallaby in our configuration settings docs.

module.exports = function(wallaby) {
  const wallabyWebpack = require('wallaby-webpack');
  const path = require('path');
  const fs = require('fs');

  const specPattern = '/**/*spec.ts';
  const angularConfig = require('./angular.json');

  const projects = Object.keys(angularConfig.projects).map(key => {
    return { name: key, ...angularConfig.projects[key] };
  }).filter(project => project.sourceRoot)
    .filter(project => project.projectType !== 'application' || 
                       (project.architect &&
                        project.architect.test &&
                        project.architect.test.builder === '@angular-devkit/build-angular:karma'));

  const applications = projects.filter(project => project.projectType === 'application');
  const libraries = projects.filter(project => project.projectType === 'library');

  const tsConfigFile = projects
    .map(project => path.join(__dirname, project.root, 'tsconfig.spec.json'))
    .find(tsConfig => fs.existsSync(tsConfig));

  const tsConfigSpec = tsConfigFile ? JSON.parse(fs.readFileSync(tsConfigFile)) : {};

  const compilerOptions = Object.assign(require('./tsconfig.json').compilerOptions, tsConfigSpec.compilerOptions);
  compilerOptions.emitDecoratorMetadata = true;

  return {
    files: [
      { pattern: path.basename(__filename), load: false, instrument: false },
      ...projects.map(project => ({
        pattern: project.sourceRoot + '/**/*.+(ts|js|css|less|scss|sass|styl|html|json|svg)',
        load: false
      })),
      ...projects.map(project => ({
        pattern: project.sourceRoot + specPattern,
        ignore: true
      })),
      ...projects.map(project => ({
        pattern: project.sourceRoot + '/**/*.d.ts',
        ignore: true
      }))
    ],

    tests: [
      ...projects.map(project => ({
        pattern: project.sourceRoot + specPattern,
        load: false
      }))
    ],

    testFramework: 'jasmine',

    compilers: {
      '**/*.ts': wallaby.compilers.typeScript({
        ...compilerOptions,
        getCustomTransformers: program => {
          return {
            before: [
              require('@ngtools/webpack/src/transformers/replace_resources').replaceResources(
                path => true,
                () => program.getTypeChecker(),
                false
              )
            ]
          };
        }
      })
    },

    preprocessors: {
      /* Initialize Test Environment for Wallaby */
      [path.basename(__filename)]: file => `
 import '@angular-devkit/build-angular/src/angular-cli-files/models/jit-polyfills';
 import 'zone.js/dist/zone-testing';
 import { getTestBed } from '@angular/core/testing';
 import { BrowserDynamicTestingModule,  platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';

 getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());`
    },

    middleware: function(app, express) {
      const path = require('path');

      applications.forEach(application => {
        if (
          !application.architect ||
          !application.architect.test ||
          !application.architect.test.options ||
          !application.architect.test.options.assets
        ) {
          return;
        }

        application.architect.test.options.assets.forEach(asset => {
          if (asset && !asset.glob) {
            // Only works for file assets (not globs)
            // (https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/asset-configuration.md#project-assets)
            app.use(asset.slice(application.sourceRoot.length), express.static(path.join(__dirname, asset)));
          }
        });
      });
    },

    env: {
      kind: 'chrome'
    },

    postprocessor: wallabyWebpack({
      entryPatterns: [
        ...applications
          .map(project => project.sourceRoot + '/polyfills.js')
          .filter(polyfills => fs.existsSync(path.join(__dirname, polyfills.replace(/js$/, 'ts')))),
        path.basename(__filename),
        ...projects.map(project => project.sourceRoot + specPattern.replace(/ts$/, 'js'))
      ],

      module: {
        rules: [
          { test: /\.css$/, loader: ['raw-loader'] },
          { test: /\.html$/, loader: 'raw-loader' },
          {
            test: /\.ts$/,
            loader: '@ngtools/webpack',
            include: /node_modules/,
            query: { tsConfigPath: 'tsconfig.json' }
          },
          { test: /\.styl$/, loaders: ['raw-loader', 'stylus-loader'] },
          { test: /\.less$/, loaders: ['raw-loader', { loader: 'less-loader' }] },
          {
            test: /\.scss$|\.sass$/,
            loaders: [{ loader: 'raw-loader' }, { loader: 'sass-loader', options: { implementation: require('sass') } }]
          },
          { test: /\.(jpg|png|svg)$/, loader: 'raw-loader' }
        ]
      },

      resolve: {
        extensions: ['.js', '.ts'],
        modules: [
          wallaby.projectCacheDir,
          ...(projects.length ? projects.filter(project => project.root)
            .map(project => path.join(wallaby.projectCacheDir, project.root)) : []),
          ...(projects.length ? projects.filter(project => project.sourceRoot)
            .map(project => path.join(wallaby.projectCacheDir,project.sourceRoot)) : []),
          'node_modules'
        ],
        alias: libraries.reduce((result, project) => {
          const alias =  project.name.replace(/([a-zA-Z])(?=[A-Z])/g, '$1-').toLowerCase();
          result[alias] = path.join(wallaby.projectCacheDir, project.sourceRoot, 'public-api');
          return result;
        }, {})
      }
    }),

    setup: function() {
      window.__moduleBundler.loadTests();
    }
  };
};

Jest

If you have configured your project to use Angular Jest Builder or are using an Nx/Nrwl workspace, you can use Wallaby without any configuration.

If you have a custom configuration Angular/Jest configuration then please refer to our Jest docs.