How to test Angular schematics?
Tags: angular, schematics, debug, testing
Published: 2022-04-26 21:50:01

_Schematics are a set of instructions for transforming a software project by generating or modifying code._ Source: [Angular docs](https://angular.io/guide/schematics).

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](https://github.com/angular/angular-cli/blob/ef23b39dd8959c57acdbc14e02ff1a2d9ea652cb/packages/schematics/angular/class/index_spec.ts) can be found in the Angular's CLI source code: 

```ts
  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');
  });
```

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](https://github.com/angular/angular-cli/blob/ef23b39dd8959c57acdbc14e02ff1a2d9ea652cb/packages/schematics/angular/ng-new/index.ts#L64-L65) 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](https://jestjs.io/) as your testing framework, you can leverage its [snapshot testing](https://jestjs.io/docs/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](https://docs.npmjs.com/cli/v8/commands/npm-link) command as described in the [angular docs](https://angular.io/guide/schematics-for-libraries#link-the-library).

💡 There is also another way - using [verdaccio](https://verdaccio.org/). This can be automated by creating a script:

```js
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/`
);
```

By the way, the full script can be found in my [schematics-testing repo](https://github.com/znikola/schematics-testing).

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: 

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

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](https://code.visualstudio.com/docs/nodejs/nodejs-debugging) (as schematics are just running in Node after all). 

💡Here is a snippet you can paste to your testing app's `launch.json` file:

```json
    {
      "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"]
    }
```

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](https://code.visualstudio.com/Docs/editor/debugging#_run-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](https://github.com/znikola/schematics-testing).

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.