DEV Community

Devin Shoemaker
Devin Shoemaker

Posted on • Updated on <time datetime="2020-07-17T03:10:37Z" class="date">Jul 17, 2020</time>

Executing External Schematics in an Nx Plugin

Nx plugins make it easy to execute an existing schematic. Examples of this can be seen in many of the official Nx plugins. For instance, both the @nrwl/react and @nrwl/angular application schematics execute the @nrwl/cypress:cypress-project schematic. With this approach, you can leverage the many schematics that Nrwl and other developers have created.

Implementation

First, we need to import the function that lets us achieve this:

import { externalSchematic } from '@angular-devkit/schematics';
Enter fullscreen mode Exit fullscreen mode

Next, you can execute the external schematic:

return externalSchematic('@nrwl/react', 'application', {
  ...options,
  routing: true,
  unitTestRunner: 'none',
  skipWorkspaceJson: true
});
Enter fullscreen mode Exit fullscreen mode

The first argument in this function is the library containing the schematic that you wish to execute. The second is the actual schematic that you wish to execute. Finally, the third argument is an object containing the schema options that you wish to pass to the schematic.

In the example above from my @nxtend/ionic-react plugin, I am executing the official @nrwl/react:application schematic, spreading the options passed into the schematic I am building, and overriding a few of those options. This kind of approach can be incredibly useful for extending existing schematics and then making tweaks to them.

One thing to note is that your user must have the library containing the external schematic added to their package.json. It's common practice to add this library for the user in your schematic if they don't have it already. You can read about how to do this in my other blog post, Building an Nx Plugin to Add Dependencies to a Project.

Testing

Jest makes it easy to unit test this kind of code through the use of spies.

import * as ngSchematics from '@angular-devkit/schematics';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import { join } from 'path';

// ...

let appTree: Tree;

const options: ApplicationSchematicSchema = {
  name: 'test',
  skipFormat: false,
  unitTestRunner: 'jest',
  e2eTestRunner: 'cypress',
  linter: Linter.EsLint,
  disableSanitizer: false,
};

const testRunner = new SchematicTestRunner(
  '@nxtend/ionic-react',
  join(__dirname, '../../../collection.json')
);

beforeEach(() => {
  appTree = createEmptyWorkspace(Tree.empty());
});

// ...

it('should call the @nrwl/react:application schematic', async () => {
  const externalSchematicSpy = jest.spyOn(ngSchematics, 'externalSchematic');
  await testRunner
    .runSchematicAsync('application', options, appTree)
    .toPromise();

  expect(externalSchematicSpy).toBeCalledWith(
    '@nrwl/react',
    'application',
    expect.objectContaining({
      routing: true,
      unitTestRunner: 'none',
      skipWorkspaceJson: true
    })
  );
});
Enter fullscreen mode Exit fullscreen mode

First, ensure that you have a test runner configured for your unit tests:

const testRunner = new SchematicTestRunner(
  '@nxtend/ionic-react',
  join(__dirname, '../../../collection.json')
);
Enter fullscreen mode Exit fullscreen mode

When creating our test runner, we define the plugin that we testing as well as the collections.json associated with the plugin.

Next, we configure our Jest spy for Angular schematics:

const externalSchematicSpy = jest.spyOn(ngSchematics, 'externalSchematic');
Enter fullscreen mode Exit fullscreen mode

Next, we use the test runner to execute the schematic that we are testing:

await testRunner
  .runSchematicAsync('application', options, appTree)
  .toPromise();
Enter fullscreen mode Exit fullscreen mode

The runSchematicAsync(...) function accepts three arguments: the schematic being executed, the options being passed to the schematic, and the app tree that the schematic is being executed on.

Finally, we make our assertion:

expect(externalSchematicSpy).toBeCalledWith(
  '@nrwl/react',
  'application',
  expect.objectContaining({
    routing: true,
    unitTestRunner: 'none',
    skipWorkspaceJson: true
  })
);
Enter fullscreen mode Exit fullscreen mode

Here we are asserting that the @nrwl/react:application schematic is being executed, and that the options passed into this schematic contain a few options that we know to be true.

Conclusion

The ability to execute external schematics adds a whole new level of reusability within your own Nx plugins as well as the community as a whole. There's no need to reinvent the wheel, Nx plugins allow you to leverage what the community has already built.

References

Nx Plugin Guide

Authoring Schematics by Angular

@nxtend/ionic-react

Discussion (0)