<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Nick Schimek]]></title><description><![CDATA[I am a full stack developer living in Chicago, IL.  I currently work for TransUnion, where I design and develop enterprise web applications primarily using Java]]></description><link>https://nick.schimek.us</link><generator>RSS for Node</generator><lastBuildDate>Mon, 25 May 2026 06:46:59 GMT</lastBuildDate><atom:link href="https://nick.schimek.us/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Converting an Angular Project to the Jest Framework]]></title><description><![CDATA[Ugh, unit tests are your first post?
Yes!  I am a huge fan of unit tests, and I'm here today to hopefully make your life easier writing them in Angular.
As developers, we are responsible for not only writing good, clean code but also comprehensive un...]]></description><link>https://nick.schimek.us/converting-an-angular-project-to-the-jest-framework</link><guid isPermaLink="true">https://nick.schimek.us/converting-an-angular-project-to-the-jest-framework</guid><category><![CDATA[Angular]]></category><category><![CDATA[Jest]]></category><category><![CDATA[Testing]]></category><category><![CDATA[unit testing]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Nick Schimek]]></dc:creator><pubDate>Thu, 17 Feb 2022 03:45:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1644897307504/1SL4dLyQN.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-ugh-unit-tests-are-your-first-post">Ugh, unit tests are your first post?</h1>
<p>Yes!  I am a huge fan of unit tests, and I'm here today to hopefully make your life easier writing them in Angular.</p>
<p>As developers, we are responsible for not only writing good, clean code but also comprehensive unit tests to accompany that code.  Often times, unit tests can feel a bit like a chore, and they are not necessarily a magic bullet at preventing production defects.  However, as I like to say: "they help me sleep at night."  By this mean, I can be relatively confident that if I write comprehensive unit tests, which cover all lines and branches, my code is functioning the way I believe it should be.</p>
<p>Simply put, unit tests are important.  Even if you're just playing around with hobby projects, developing skill for writing effective unit tests is arguably just as important as writing the code to begin with, and you can rest assured any professional development job will, at the very least, require you to achieve a certain line coverage percentage (we also enforce branch percentage on my team).</p>
<h2 id="heading-ok-so-you-mentioned-angular">Ok, so you mentioned Angular...</h2>
<p>Yes, I did.  When I first learned Angular, I was disappointed at how cumbersome it was to write unit tests.  Not only do we have to juggle multiple different <code>querySelector()</code> strategies and dive in through <code>debugElement</code>s constantly just to put a specific element under test, we also had to spin up a full-on webserver just to run the tests.  That's to say nothing of setting up your <code>TestBed</code> to begin with, which as a beginner, was quite the challenge for me to wrap my head around.</p>
<p>Well, a combination of <a target="_blank" href="https://github.com/ngneat/spectator">Spectator</a> and <a target="_blank" href="https://github.com/ike18t/ng-mocks">NgMocks</a> largely solves the issues related to the cumbersome API and <code>TestBed</code> dependencies.  That topic alone is worth another article, but today we are here to talk about the <a target="_blank" href="https://jestjs.io/">Jest Framework</a>, which addresses my webserver complaint.</p>
<h1 id="heading-what-is-jest">What is Jest?</h1>
<blockquote>
<p>Jest is a delightful JavaScript Testing Framework with a focus on simplicity. -- jestjs.io</p>
</blockquote>
<p>Beyond that, it's an alternative to Jasmine/Karma that lets you run tests one at a time using a Visual Studio Code extension.  The end result is a unit testing experience that's much more similar to Java IDEs: click run at whatever level you desire (test, file, project), and see your results.</p>
<h2 id="heading-benefits">Benefits</h2>
<ul>
<li><p>Does not require a webserver to run unit tests.  Makes running individual one-off tests much faster.</p>
</li>
<li><p>Do not need to juggle cumbersome <code>fit()</code> and <code>fdescribe()</code> code modifications to run individual tests or suites.  Simply click Run.</p>
</li>
<li><p>No need for a Headless browser to run in a CI/CD environment.</p>
</li>
<li><p><em>Claims</em> to be 2-3x faster than Karma, and while this is usually true locally, in Jenkins I've observed it to be about the same speed.</p>
</li>
<li><p>Has a "watch" mode, which will automatically run unit tests for files that have changed since the last commit.</p>
</li>
</ul>
<h2 id="heading-downsides">Downsides</h2>
<ul>
<li><p>Jest uses JSDOM, which is not a "real" browser.  So if you have tests that are specifically testing browser compatibility, Jasmine + Karma remains a better choice as they simulate actual browser behavior.</p>
</li>
<li><p>If you use <code>window.localStorage</code>, you will need setup a global mock due to the above limitation.  <a target="_blank" href="https://ordina-jworks.github.io/testing/2018/08/03/testing-angular-with-jest.html">This fantastic article has an example</a>.</p>
</li>
<li><p>I have experienced challenges getting my Jest tests to pass in Jenkins initially, although this was resolved after some troubleshooting.  Still, the fact they passed locally and failed in the CI/CD environment is worrisome.  (More on that later.)</p>
</li>
<li><p>Will require test code changes to get old tests running and passing.  If you are already using Spectator and ngMocks to their fullest extent, these will be fairly minimal.</p>
</li>
</ul>
<h1 id="heading-ok-great-you-are-a-unit-test-nerd-and-like-jest-why-write-a-guide-about-it">Ok, great - you are a unit test nerd and like Jest.  Why write a guide about it?</h1>
<p>When I set about migrating a fairly large (1k+ unit tests, 99% coverage) project to Jest, I found myself requiring many different guides and posts to achieve the same level of functionality I had grown used to with Jasmine in terms of matchers and the ability to conditionally mock.  Some of the information out there is also old, and may not be applicable anymore, or configured differently now.</p>
<p>This guide represents my experience, which took place just a few months ago.  So everything below is relatively current, and I just tested it again as I wrote this guide.</p>
<h1 id="heading-how-to-setup-jest">How to Setup Jest</h1>
<h2 id="heading-remove-jasmine-and-karma">Remove Jasmine and Karma</h2>
<p>First, you begin by removing Jasmine and Karma.</p>
<pre><code>$ npm uninstall jasmine<span class="hljs-operator">-</span>core @types<span class="hljs-operator">/</span>jasmine karma karma<span class="hljs-operator">-</span>chrome<span class="hljs-operator">-</span>launcher karma<span class="hljs-operator">-</span>coverage karma<span class="hljs-operator">-</span>jasmine karma<span class="hljs-operator">-</span>jasmine<span class="hljs-operator">-</span>html<span class="hljs-operator">-</span>reporter
</code></pre><p><em>Note: these are the Jasmine and Karma-related packages installed on a fresh Angular 13 project.  If you have any others in your project, be sure to remove those as well.</em></p>
<h3 id="heading-remove-configuration-files">Remove configuration files</h3>
<p>You should also remove the following files:</p>
<ul>
<li>src/test.js</li>
<li>karma.conf.js</li>
</ul>
<h2 id="heading-install-jest-and-supporting-libraries">Install Jest and supporting libraries</h2>
<p>Install the following libraries:</p>
<pre><code>$ <span class="hljs-built_in">npm</span> install jest jest-preset-angular @types/jest @testing-library/jest-dom jest-<span class="hljs-keyword">when</span> @types/jest-<span class="hljs-keyword">when</span> --save-dev
</code></pre><p><em>Not all of these are required, but installing them all will get you to effectively the same functionality that Karma/Jasmine provided.  If you did not use <code>.withArgs()</code> in Jasmine, you do not need <code>jest-when</code>, for example.</em></p>
<p>I would also recommend installing Jest CLI globally, this has helped reduce some issues getting tests to run again after re-installing a project:</p>
<pre><code>$ npm install <span class="hljs-operator">-</span>g jest<span class="hljs-operator">-</span>cli
</code></pre><h2 id="heading-update-configurations">Update configurations</h2>
<ul>
<li>If there is a <code>jest</code> field present in your <strong>package.json</strong> file, remove it.  You will be replacing this with a setup-jest.ts file.</li>
<li>Update <strong>tsconfig.spec.json</strong> by replacing <code>jasmine</code> with <code>jest</code> and adding <code>@testing-library/jest-dom</code> to the <code>types</code> array (under <code>compilerOptions</code>):</li>
</ul>
<pre><code class="lang-json">  <span class="hljs-string">"compilerOptions"</span>: {
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./out-tsc/spec"</span>,
    <span class="hljs-attr">"types"</span>: [
      <span class="hljs-string">"jest"</span>,
      <span class="hljs-string">"@testing-library/jest-dom"</span>
    ]
  },
</code></pre>
<ul>
<li>Also, remove <code>src/test.ts</code> from <code>files</code>:</li>
</ul>
<pre><code class="lang-json">  <span class="hljs-string">"files"</span>: [
    <span class="hljs-string">"src/polyfills.ts"</span>
  ],
</code></pre>
<ul>
<li>Add the following to <strong>tsconfig.json</strong> under <code>compilerOptions</code>:</li>
</ul>
<pre><code class="lang-json"><span class="hljs-string">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
</code></pre>
<ul>
<li>Create a new file called <strong>jest.config.js</strong> in your project's root directory, and populate it with the following:</li>
</ul>
<pre><code class="lang-js"><span class="hljs-comment">// this will force Jest to use the AOT compiler in Jenkins - uncomment if you have issues with test failures in Jenkins - see below for more info</span>
<span class="hljs-comment">// require('jest-preset-angular/ngcc-jest-processor');</span>

<span class="hljs-keyword">const</span> tsJestPreset = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jest-preset-angular/jest-preset'</span>).globals[<span class="hljs-string">'ts-jest'</span>];

<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">preset</span>: <span class="hljs-string">"jest-preset-angular"</span>,
  <span class="hljs-attr">roots</span>: [<span class="hljs-string">'src'</span>],
  <span class="hljs-attr">setupFilesAfterEnv</span>: [<span class="hljs-string">"&lt;rootDir&gt;/src/setup-jest.ts"</span>],
  <span class="hljs-attr">verbose</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">testURL</span>: <span class="hljs-string">"http://localhost"</span>,
  <span class="hljs-attr">globals</span>: {
    <span class="hljs-string">'ts-jest'</span>: {
      ...tsJestPreset,
      <span class="hljs-attr">tsconfig</span>: <span class="hljs-string">"&lt;rootDir&gt;/tsconfig.spec.json"</span>
    }
  }
}
</code></pre>
<blockquote>
<p>The <code>require('jest-preset-angular/ngcc-jest-processor')</code> line above was necessary to get a work project's tests running successfully in Jenkins.  For some reason, Jenkins appeared to be using the JIT compiler while running Jest unit tests.  <a target="_blank">More information available in this GitHub Issue</a>.</p>
</blockquote>
<ul>
<li>Create a file called <strong>jsconfig.json</strong> (note this starts with a <strong>j</strong>, is different than tsconfig.json) in your project's root directory and populate it with the following:</li>
</ul>
<pre><code class="lang-js">{
  <span class="hljs-string">"typeAcquisition"</span>: {
    <span class="hljs-string">"include"</span>: [
      <span class="hljs-string">"jest"</span>
    ]
  }
}
</code></pre>
<p><em>I had to do this to get Visual Studio Code's IntelliSense to stop trying to use Jasmine for everything when I converted an old project.  I do not think this will be necessary if you remove <strong>all traces</strong> of Jasmine/Karma first (and it was not necessary for a new project), but I figured I would leave it here just in case any poor soul faces this issue.</em></p>
<ul>
<li>Create a file called <strong>setup-jest.ts</strong> in your project's <code>src</code> directory and populate it with the following:</li>
</ul>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> <span class="hljs-string">'jest-preset-angular/setup-jest'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'@testing-library/jest-dom/extend-expect'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'jest-when'</span>
</code></pre>
<ul>
<li><p>Remove the entire <code>test</code> block from <strong>angular.json</strong>.  This is strictly optional, but if you run <code>ng test</code> it will fail.</p>
</li>
<li><p>Finally, if you are using <a target="_blank" href="https://github.com/ngneat/spectator">Spectator</a> (and you should be!), update the <code>schematics</code> property in <strong>angular.json</strong> to include the following:</p>
</li>
</ul>
<pre><code class="lang-json"><span class="hljs-string">"@ngneat/spectator:specator-component"</span>: {
  <span class="hljs-attr">"test"</span>: <span class="hljs-string">"jest"</span>
}
</code></pre>
<h2 id="heading-packagejson-changes">package.json Changes</h2>
<p>The changes to package.json are very straightfoward, just update the <code>test</code> script:</p>
<pre><code class="lang-json">  <span class="hljs-string">"test"</span>: <span class="hljs-string">"jest --coverage"</span>, <span class="hljs-comment">// I like to add --coverage to display a coverage report</span>
  <span class="hljs-string">"test:watch"</span>: <span class="hljs-string">"jest --watch"</span>, <span class="hljs-comment">// optional - automatically runs tests as files change</span>
  <span class="hljs-string">"test:ci"</span>: <span class="hljs-string">"jest --runInBand"</span>  <span class="hljs-comment">// optional - for CI/CD environments</span>
</code></pre>
<p>You can then use <code>npm run test</code> then to run all tests, which should be quite fast - remember, no webserver!</p>
<p><code>jest --watch</code> (<code>npm run test:watch</code> above) is pretty cool: as you make changes to  your source files, Jest will automatically run tests for that file, as well as <em>any related files</em>.  </p>
<h1 id="heading-visual-studio-code-extensions">Visual Studio Code Extensions</h1>
<p>To get the most out of Jest, you will want to install the following extensions on Visual Studio Code:</p>
<ul>
<li><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer">Test Explorer UI</a> </li>
<li><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.test-adapter-converter">Test Adapter Converter</a></li>
<li><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest">Jest</a></li>
</ul>
<p>I also like to set <code>jest.autoRun</code> to <code>off</code> in my <code>settings.json</code>, otherwise all tests will run at start-up which for large projects can be cumbersome.</p>
<blockquote>
<p><strong>IMPORTANT:</strong> Make sure you are opening your project in VS Code from the project's root directory (the same folder <code>src</code> is found in).  Otherwise, none of the Jest UI extensions will work.   When I created a new Angular project via a Docker Container, VS was opening <code>/workspace/angular</code> instead of <code>/workspace/angular/angular-demo</code> (my project's root), and as a result, none of the test extensions above worked.</p>
</blockquote>
<p><strong><em>Once you have the above extensions installed, you can easily run your unit tests from either the newly-added Testing screen, or by clicking the little green play icons within your spec files!</em></strong></p>
<p>...but they may fail, read on for the bad news.</p>
<h1 id="heading-spec-file-updates">Spec File Updates</h1>
<p>Last but not least, if you have existing unit tests, unfortunately some of your Jasmine implementations will need to be converted.  </p>
<blockquote>
<p>Using Spectator is <strong>immensely</strong> helpful with this effort, as you can simply change your <code>@ngneat/spectator</code> imports to <code>@ngneat/spectator/jest</code> and all Spectator-related functionality will automatically be converted!</p>
</blockquote>
<p>Here are some of the updates I have had to make:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Jasmine</td><td>Jest</td></tr>
</thead>
<tbody>
<tr>
<td><code>spyOn(class, 'method')</code></td><td><code>jest.spyOn(class, 'method')</code></td></tr>
<tr>
<td><code>spy = jasmine.createSpyObj('name', ['key'])</code></td><td><code>spy = { key: jest.fn() }</code></td></tr>
<tr>
<td><code>.and.returnValue()</code></td><td><code>.mockReturnValue()</code></td></tr>
<tr>
<td><code>spy.key.withArgs([args]).and.returnValue()</code></td><td>when <code>(spy.key).calledWith([...args]).mockReturnValue()</code></td></tr>
<tr>
<td><code>spyOn(...).and.callFake(() =&gt; {...})</code></td><td><code>jest.spyOn(...).mockImplementation(() =&gt; {})</code></td></tr>
<tr>
<td><code>jasmine.any, jasmine.objectContaining</code></td><td><code>expect.any, expect.objectContaining</code></td></tr>
<tr>
<td><code>element.innerText</code></td><td><code>element.textContent</code></td></tr>
</tbody>
</table>
</div><blockquote>
<p>Note: thanks to <a target="_blank" href="https://www.xfive.co/blog/testing-angular-faster-jest/">this blog post</a> for helping me get started with most of the above material.</p>
</blockquote>
<h1 id="heading-final-thoughts">Final Thoughts</h1>
<p>So...is it worth it?  Personally, I think so.  It took a while to convert over 1k existing tests to be Jest-friendly, but once I got rolling it was largely the same few different scenarios that had to be converted over and over again.  The the ability to run tests one-off quickly without having to use <code>fit()</code> or <code>fdescribe()</code> and spin up a webserver has really made the unit testing process in Angular a lot smoother and enjoyable.</p>
<p>You can check out my <a target="_blank" href="https://github.com/nschimek/angular-demo">Angular Demo Project here on GitHub</a> to see all of the above in action.  Have fun!</p>
<p>And feel free to leave comments with any questions or comments.  Thanks for reading!</p>
]]></content:encoded></item></channel></rss>