DEV Community

Cover image for How to test Angular schematics?
Nikola Zariฤ‡
Nikola Zariฤ‡

Posted on

How to test Angular schematics?

Schematics are a set of instructions for transforming a software project by generating or modifying code. Source: Angular docs.

The information about Angular schematics on the Internet is a bit scarce, making the testing a necessary tool for every developer writing them.

We can approach testing schematics in a couple of ways:

  • Integration testing
  • Publishing locally
  • Debugging

Integration testing ๐Ÿงช

This boils down to creating a spec file, and testing schematics in-memory.

An example can be found in the Angular's CLI source code:

  let appTree: UnitTestTree;
  beforeEach(async () => {
    appTree = await schematicRunner.runSchematicAsync('workspace', workspaceOptions).toPromise();
    appTree = await schematicRunner
      .runSchematicAsync('application', appOptions, appTree)
      .toPromise();
  });

  it('should create just the class file', async () => {
    const tree = await schematicRunner
      .runSchematicAsync('class', defaultOptions, appTree)
      .toPromise();
    expect(tree.files).toContain('/projects/bar/src/app/foo.ts');
    expect(tree.files).not.toContain('/projects/bar/src/app/foo.spec.ts');
  });
Enter fullscreen mode Exit fullscreen mode

In the above code snippet, we first setup the test in beforeEach:

  • runSchematicAsync('workspace', ...) prepares schematics workspace which just scaffolds an empty-ish npm project and adds angular.json.
  • schematicRunner.runSchematicAsync('application', ...) - creates the Angular application inside of the generated workspace.
  • As a side note, under the hood both workspace and application schematics are executed as part of ng new command.

After this we can execute the schematic which we are testing runSchematicAsync('class', ...) and assert the result of its execution.

๐Ÿ“– This approach is pretty standard and straightforward, and quite fast as the execution is in-memory.

๐Ÿ’ก If you are using Jest as your testing framework, you can leverage its snapshot testing in order to assert the generated files' content. ๐Ÿคฏ

Publishing locally ๐Ÿ“ฃ

It is recommended to try our schematics first, before publishing them into the wild.

๐Ÿ“– Testing in this way could reveal some oversights made during the integration testing due to preparing the workspace / application state too well for the test.
It's also very satisfying to see your hard work in action before actually publishing schematics. ๐Ÿ˜‰

One way to achieve this is by using npm link command as described in the angular docs.

๐Ÿ’ก There is also another way - using verdaccio. This can be automated by creating a script:

import { exec, execSync } from "child_process";

// 1. run verdaccio with a pre-defined configuration
exec(
  "./node_modules/verdaccio/bin/verdaccio --config ./scripts/config.yaml"
);
// 2. point to verdaccio only for our `@test` scope
execSync(`npm config set @test:registry http://localhost:4873/`);
// 3. build schematics
execSync("yarn build:schematics");
// 4. publish schematics to verdaccio
execSync(
  `yarn publish --cwd dist/schematics-testing/schematics/package.json --new-version 0.0.1 --registry=http://localhost:4873/`
);
Enter fullscreen mode Exit fullscreen mode

By the way, the full script can be found in my schematics-testing repo.

We can now switch to our testing application (generated via ng new) and execute our schematics (e.g. ng add @somescope/somepackagename). As long as the verdaccio is running, you will be able to consume your locally published schematics.

After we're done with testing, you can close the script and it will point back to npmjs registry:

process.once("SIGINT", function () {
  execSync(`npm config set @test:registry https://registry.npmjs.org/`);
  verdaccioProcess?.kill();
  process.exit();
});
Enter fullscreen mode Exit fullscreen mode

This approach is more scalable if you are creating schematics for many libraries.

Debugging ๐Ÿž

You can always just console.log the state of your code, but sometimes things get hairy and you need to go step by step through the code in order to better understand what's going on.

๐Ÿ“– If you're using VSCode, you can debug the schematics as if you would debug any other Node application (as schematics are just running in Node after all).

๐Ÿ’กHere is a snippet you can paste to your testing app's launch.json file:

    {
      "type": "node",
      "request": "launch",
      "name": "Debug schematics",
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}/node_modules/@angular/cli/bin/ng",
      "args": [
        "add",
        "@somescope/somepackagename@latest",
        "--skip-confirmation",
        "--param1=value1",
        "--param2=value2"
      ],
      "console": "integratedTerminal",
      "outFiles": ["${workspaceFolder}/node_modules/@somescope/**/*.js"]
    }
Enter fullscreen mode Exit fullscreen mode

Before you can actually put any break-points and debug, make sure your schematics are installed in your testing app's node_modules. Running ng add @somescope/somepackagename will ensure this.
After that's done, you can open any .js file from node_modules/@somescope/** and add a break-point.
To run the schematics again, you can switch to Run and Debug view, select Debug Schematics from the configuration drop-down menu, run it, and voila - the execution will stop at your break-point. ๐ŸŽ‰

Conclusion ๐ŸŽฌ

You can see all three approaches configured in my schematics-testing repo.

Testing schematics is not something you should be afraid of.
Each approach has its own benefits:

  • Integration testing is fast, and can be executed on the CI.
  • Publishing locally is highly recommended, and can save you from having to publish again if you discover something is not working as expected.
  • Debugging is very useful for those situations when you are just puzzled what's going on, and you have to dive into the code to better understand it.

Top comments (0)