Tutorials: Angular CLI tutorial

Wallaby.js runs your JavaScript tests immediately as you type and displays execution results in your code editor. Wallaby also provides beautiful test and code coverage reports updated in realtime.

In this tutorial we’re going to share how use Wallaby.js in a new Angular CLI project created with Angular’s ng command.

Select your editor

The instructions in this tutorial are adjusted to your editor of choice. Please select your editor by clicking on your editor’s logo below before proceeding with the tutorial:

Select configuration mode

Wallaby can be run either with or without a configuration file. We recommend using Automatic configuration - Wallaby requires no configuration file, however if you choose Manual configuration then the tutorial will be adjusted with additional sections demonstrating the contents of the configuration file.

Automatic
Manual

Automatic configuration is supported starting from Angular CLI v8.2.0, so if you are using a previous version please update your Angular CLI package version or else switch to Manual configuration mode. We’ve structured this tutorial so you can follow along. If you don’t want to follow along then you may skip ahead and find the end result in this github repo.

Install Wallaby

First, if you haven’t done so already, go ahead and download and install Wallaby.js.

Create a new project

Now that we’re ready to start, open your terminal and change directory to where we will create our angular app. Run the following command:

ng new wallaby-angular-cli-project

You will be prompted for two options:

  • Would you like to add Angular routing? N
  • Which stylesheet format would you like to use? CSS

At this point, the ng cli tool has created a new folder for us named wallaby-angular-cli-project with our base project.

Add Project Dependencies

Wallaby is designed to work with a number of frameworks and expects that your project has the wallaby-webpack dependency that is necessary to run with Angular CLI projects.

The wallaby-webpack package is a Wallaby.js post-processor that incrementally bundles file changes and processes them with webpack before Wallaby.js runs your tests. If you’re interested in how it works, you can explore the open source wallaby-webpack package.

npm install wallaby-webpack --save-dev

Configure Wallaby.js

Next, open the new folder in your editor and create a new file in the project root, named wallaby.js. Copy the configuration shown below and paste it into the wallaby.js file. 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 (we have configured it for you). 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();
    }
  };
};

Start Wallaby.js

At this point, Wallaby.js is configured and is ready to be started. If this is the first time you are using wallaby, it may take a minute or two to install all the dependencies it needs.

To start Wallaby without configuration in VS Code you may run Select Configuration command and then select Automatic Configuration <project directory> option once. After that you may keep using Wallaby Start command as usual and it will start Wallaby with automatic configuration.

To start Wallaby without configuration in Atom you may use the Select for Wallaby.js Automatic Configuration context menu item for your project folder in the project’s file tree. After that you may keep using Wallaby Start command as usual and it will start Wallaby with automatic configuration.

To start Wallaby without configuration in Sublime Text you may use the Select for Wallaby.js Automatic Configuration context menu item for your project folder in the project’s file tree. After that you may keep using Wallaby Start command as usual and it will start Wallaby with automatic configuration.

To start Wallaby without a configuration file in Visual Studio, you may use the Start Wallaby.js (Automatic Configuration) context menu item for your project folder in the Solution Explorer. After the first start, the selected project will be remembered for your solution and Wallaby can be started with Tools->Start Wallaby.js (Alt-W, 1).

To start Wallaby without a configuration file in JetBrains IDEs, you may edit (or create a new) wallaby.js Run Configuration by selecting Edit configurations from the drop-down menu in the top right-hand corner of the navigation bar, or from the Run menu. In the run configuration editor set Configuration Type filed value to Automatic.

In VS Code, launch the Command Pallete (Ctrl/Cmd + Shift + P) and then choose the Wallaby.js: Start command.

In Atom, launch the Command Pallete (Ctrl/Cmd + Shift + P) and then choose the Wallaby: Start command.

In Sublime Text, launch the Command Pallete (Ctrl/Cmd + Shift + P) and then choose the Wallaby.js: Start command.

In Visual Studio, right click on your Wallaby.js configuration file in your Solution Explorer, and then select Start Wallaby.js.

In your IntelliJ Editor, create a new wallaby.js Run Configuration. Select Edit configurations from the drop-down menu in the top right-hand corner of the navigation bar, or from the Run menu. Add a new Wallaby.js Configuration, specify the path to your wallaby.js configuration file and click OK. Once you have created and saved the configuration, you can start it. If you need help creating a run configuration, you may refer to JetBrains docs.

jetbrains run configuration

After Wallaby.js has started, navigate to your application and test files (app.component.ts, app.component.spec.ts), you will see coverage indicators in the gutter of your code editor:

  • Gray squares mean that the line contains code that was not run by any of your tests.
  • Green squares mean that the line contains code that was run by at least one of your tests.
  • Yellow squares mean that the line contains code that was only partially covered by your tests.
  • Red squares mean that the line is the source of an error, a failed assertion, or is part of a stack trace for an error.
  • Pink squares mean that the source line is on the execution path of a failing test.

Open up src/app/app.component.spec.ts to confirm that everything is working. It should look like this:

file markers in app.component.spec.ts file markers in app.component.spec.ts file markers in app.component.spec.ts file markers in app.component.spec.ts file markers in app.component.spec.ts

Break some tests

Let’s break our tests by updating the title property of app.Component.ts.

Open src/app/app.component.ts and change the title from wallaby-angular-cli-project to Wallaby Angular CLI Project.

When Wallaby.js is running (as it should be now), you will see that you now have some failing tests.

In src/app/app.component.spec.ts you will see that some line markers have changed to pink, and your Wallaby.js Test output window shows you that two of your tests no longer equal their expected values.

At this point, you might like to Toggle Wallaby’s Panel by launching the Command Pallete and selecting Wallaby.js: Toggle Tests View.

At this point, you might like to Toggle Wallaby’s Panel by launching the Command Pallete and selecting Wallaby: Toggle Panel.

broken app broken app broken app broken app broken app

Fix tests

After reviewing why the tests have broken, we’re happy with the actual values so can go ahead and updated the tests to make them pass. Now do that by updating the expected values in src/app/app.component.spec.ts.

You’ll see that the error has gone away, and our tests are passing again.

fixed tests fixed tests fixed tests fixed tests fixed tests

You’re now ready to use Wallaby to write your Angular projects. With Wallaby, you still write your tests the same way that you do today but now you get real-time in-editor feedback.

Learn More…

We’ve only covered the very basics of configuring and using Wallaby.js but the tool is capable of a whole lot more. Read through our docs to learn more about Wallaby.js features. Some notable features include:

  • Wallaby App updates in real-time and provides a strategic level view of coverage and test results.
  • Advanced Logging allows you to quickly and easily debug at runtime with keyboard shortcuts, comments, and console log.
  • Value Explorer provides the ability to explore runtime values right in your editor.

You may also learn about more Wallaby features in the VS Code tutorialJetBrains IDEs tutorialVisual Studio tutorialAtom tutorialSublime Text tutorial.

More information about Angular CLI integration can be found here.

Workspaces/Libraries

You can use the same configuration above if you are using workspaces or have libraries nested in your angular project.

Jest

If you are using Jest >= v24 and have Jest CLI configured for your Angular project then it will be used by default.

If you have Jest CLI configured for your Angular project and want to run Wallaby, you may use the config below:

module.exports = function(wallaby) {
  return {
    files: ['src/**/*.+(ts|html|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', '!src/**/*.spec.ts'],

    tests: ['src/**/*.spec.ts'],

    env: {
      type: 'node'
    },

    compilers: {
      '**/*.ts?(x)': wallaby.compilers.typeScript({
        module: 'commonjs',
        getCustomTransformers: () => {
          return {
            before: [
              require('jest-preset-angular/InlineHtmlStripStylesTransformer').factory({
                compilerModule: require('typescript')
              })
            ]
          };
        }
      }),
      '**/*.html': file => ({
        code: require('ts-jest').process(file.content, file.path, {
          globals: { 'ts-jest': { stringifyContentPathRegex: '\\.html$' } }
        }),
        map: { version: 3, sources: [], names: [], mappings: [] },
        ranges: []
      })
    },

    preprocessors: {
      'src/**/*.js': [
        file =>
          require('@babel/core').transform(file.content, {
            sourceMap: true,
            compact: false,
            filename: file.path,
            presets: [require('babel-preset-jest')]
          })
      ]
    },

    setup: function(wallaby) {
      let jestConfig = require('./package.json').jest;
      delete jestConfig.preset;
      jestConfig = Object.assign(require('jest-preset-angular/jest-preset'), jestConfig);
      jestConfig.transformIgnorePatterns.push('instrumented.*.(jsx?|html)$');
      wallaby.testFramework.configure(jestConfig);
    },

    testFramework: 'jest'
  };
};

If you are setting up Jest from scratch for your Angular project, please read this blog post.

Troubleshooting

If you get stuck or something isn’t working for you, you may find a similar solved issue in our github issues repository. If you can’t find a solution, create a new issue.