<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nikola Zarić</title>
    <description>The latest articles on DEV Community by Nikola Zarić (@znikola).</description>
    <link>https://dev.to/znikola</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F672620%2F263ffa08-5d78-4a8a-84b1-52ed8d041643.jpeg</url>
      <title>DEV Community: Nikola Zarić</title>
      <link>https://dev.to/znikola</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/znikola"/>
    <language>en</language>
    <item>
      <title>How to test Angular schematics?</title>
      <dc:creator>Nikola Zarić</dc:creator>
      <pubDate>Tue, 26 Apr 2022 21:50:01 +0000</pubDate>
      <link>https://dev.to/znikola/how-to-test-angular-schematics-2h05</link>
      <guid>https://dev.to/znikola/how-to-test-angular-schematics-2h05</guid>
      <description>&lt;p&gt;&lt;em&gt;Schematics are a set of instructions for transforming a software project by generating or modifying code.&lt;/em&gt; Source: &lt;a href="https://angular.io/guide/schematics"&gt;Angular docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The information about Angular schematics on the Internet is a bit scarce, making the testing a necessary tool for every developer writing them.&lt;/p&gt;

&lt;p&gt;We can approach testing schematics in a couple of ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integration testing&lt;/li&gt;
&lt;li&gt;Publishing locally&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integration testing 🧪
&lt;/h2&gt;

&lt;p&gt;This boils down to creating a spec file, and testing schematics in-memory.&lt;/p&gt;

&lt;p&gt;An &lt;a href="https://github.com/angular/angular-cli/blob/ef23b39dd8959c57acdbc14e02ff1a2d9ea652cb/packages/schematics/angular/class/index_spec.ts"&gt;example&lt;/a&gt; can be found in the Angular's CLI source code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;appTree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UnitTestTree&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;appTree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;schematicRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runSchematicAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workspace&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceOptions&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;appTree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;schematicRunner&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runSchematicAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appTree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should create just the class file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;schematicRunner&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runSchematicAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;class&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;appTree&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/projects/bar/src/app/foo.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/projects/bar/src/app/foo.spec.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code snippet, we first setup the test in &lt;code&gt;beforeEach&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;runSchematicAsync('workspace', ...)&lt;/code&gt; prepares schematics workspace which just scaffolds an empty-ish npm project and adds &lt;code&gt;angular.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schematicRunner.runSchematicAsync('application', ...)&lt;/code&gt; - creates the Angular application inside of the generated workspace.&lt;/li&gt;
&lt;li&gt;As a side note, &lt;a href="https://github.com/angular/angular-cli/blob/ef23b39dd8959c57acdbc14e02ff1a2d9ea652cb/packages/schematics/angular/ng-new/index.ts#L64-L65"&gt;under the hood&lt;/a&gt; both &lt;code&gt;workspace&lt;/code&gt; and &lt;code&gt;application&lt;/code&gt; schematics are executed as part of &lt;code&gt;ng new&lt;/code&gt; command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After this we can execute the schematic which we are testing &lt;code&gt;runSchematicAsync('class', ...)&lt;/code&gt; and assert the result of its execution.&lt;/p&gt;

&lt;p&gt;📖 This approach is pretty standard and straightforward, and quite fast as the execution is in-memory.&lt;/p&gt;

&lt;p&gt;💡 If you are using &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; as your testing framework, you can leverage its &lt;a href="https://jestjs.io/docs/snapshot-testing"&gt;snapshot testing&lt;/a&gt; in order to assert the generated files' content. 🤯&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing locally 📣
&lt;/h2&gt;

&lt;p&gt;It is recommended to try our schematics first, before publishing them into the wild.&lt;/p&gt;

&lt;p&gt;📖 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.&lt;br&gt;
It's also very satisfying to see your hard work in action before actually publishing schematics. 😉&lt;/p&gt;

&lt;p&gt;One way to achieve this is by using &lt;a href="https://docs.npmjs.com/cli/v8/commands/npm-link"&gt;npm link&lt;/a&gt; command as described in the &lt;a href="https://angular.io/guide/schematics-for-libraries#link-the-library"&gt;angular docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;💡 There is also another way - using &lt;a href="https://verdaccio.org/"&gt;verdaccio&lt;/a&gt;. This can be automated by creating a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. run verdaccio with a pre-defined configuration&lt;/span&gt;
&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./node_modules/verdaccio/bin/verdaccio --config ./scripts/config.yaml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 2. point to verdaccio only for our `@test` scope&lt;/span&gt;
&lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`npm config set @test:registry http://localhost:4873/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 3. build schematics&lt;/span&gt;
&lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yarn build:schematics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 4. publish schematics to verdaccio&lt;/span&gt;
&lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`yarn publish --cwd dist/schematics-testing/schematics/package.json --new-version 0.0.1 --registry=http://localhost:4873/`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way, the full script can be found in my &lt;a href="https://github.com/znikola/schematics-testing"&gt;schematics-testing repo&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;After we're done with testing, you can close the script and it will point back to npmjs registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`npm config set @test:registry https://registry.npmjs.org/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;verdaccioProcess&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is more scalable if you are creating schematics for many libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging 🐞
&lt;/h2&gt;

&lt;p&gt;You can always just &lt;code&gt;console.log&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;📖 If you're using VSCode, you can debug the schematics as if you would &lt;a href="https://code.visualstudio.com/docs/nodejs/nodejs-debugging"&gt;debug any other Node application&lt;/a&gt; (as schematics are just running in Node after all). &lt;/p&gt;

&lt;p&gt;💡Here is a snippet you can paste to your testing app's &lt;code&gt;launch.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Debug schematics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"skipFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;node_internals&amp;gt;/**"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"program"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/node_modules/@angular/cli/bin/ng"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"@somescope/somepackagename@latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--skip-confirmation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--param1=value1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--param2=value2"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"console"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integratedTerminal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"${workspaceFolder}/node_modules/@somescope/**/*.js"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you can actually put any break-points and debug, make sure your schematics are installed in your testing app's &lt;code&gt;node_modules&lt;/code&gt;. Running &lt;code&gt;ng add @somescope/somepackagename&lt;/code&gt; will ensure this.&lt;br&gt;
After that's done, you can open any &lt;code&gt;.js&lt;/code&gt; file from &lt;code&gt;node_modules/@somescope/**&lt;/code&gt; and add a break-point.&lt;br&gt;
To run the schematics again, you can switch to &lt;a href="https://code.visualstudio.com/Docs/editor/debugging#_run-view"&gt;Run and Debug view&lt;/a&gt;, select &lt;code&gt;Debug Schematics&lt;/code&gt; from the configuration drop-down menu, run it, and voila - the execution will stop at your break-point. 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion 🎬
&lt;/h2&gt;

&lt;p&gt;You can see all three approaches configured in my &lt;a href="https://github.com/znikola/schematics-testing"&gt;schematics-testing repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Testing schematics is not something you should be afraid of.&lt;br&gt;
Each approach has its own benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integration testing is fast, and can be executed on the CI.&lt;/li&gt;
&lt;li&gt;Publishing locally is highly recommended, and can save you from having to publish again if you discover something is not working as expected.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>schematics</category>
      <category>debug</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
