Debugging Jest Unit Tests the Easy Way

posted: Jan. 31, 2024 09:20 PM

As software engineers, we face many challenges that can be frustrating to solve and power through; as such, the act of writing software and the tooling we use should be joyful and out of our way as much as possible. When we get frustrated because our program is not doing what we expect, I find I often curse in a foreign language because dropping the F-bomb in an office typically doesn't go over well. I do not want to have to report to HR.

Programmers Solving Problems

Anyway, one such challenge that almost every front-end developer faces is a unit test breaking, and they need to figure out why in the shortest amount of time possible. I've seen articles (Example 1, Example 2) on the interwebs that give the direction to modify your launch.json and add a ton of settings. Then, the joy killer is to set the direct path to the Jest test file that you want to debug.

Now Jest says:

Jest is a delightful JavaScript Testing Framework with a focus on simplicity.

Most development projects I've worked on have a steady but intense pace. Developers will have to debug many different unit tests spanning multiple files, and having to modify my VS Code launch.json to debug each unit test file would kill my joy.

A developer with a failing unit test

I have recently found a better way. Let's give it a go from scratch. Here's what we're going to do from a high level:

  1. Create an Nx Integrated Angular Monorepo
  2. Use VS Code to attach the debugger when we run our tests
  3. Set a breakpoint in the scaffolded unit test
  4. Use Nx to run our tests and watch the glory of pausing at the breakpoint in our test

The Setup🔗

First, let's use nx to create our monorepo, so ensure you have Node v20.11.0 installed, but don't install that globally; instead, use NVM for your platform NVM for Mac/Linux or NVM for Windows.

Once you have Node running on your machine issue the following command:

npx create-nx-workspace@latest

then answer the following prompts:

 Where would you like to create your workspace? · DebuggingExample
 Which stack do you want to use? · angular
 Integrated monorepo, or standalone project? · integrated
 Application name · DebuggingExample
 Which bundler would you like to use? · webpack
 Default stylesheet format · scss
 Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? · No
 Test runner to use for end to end (E2E) tests · playwright
 Enable distributed caching to make your CI faster · Yes

Go to the break room, otherwise known as the kitchen in a post-COVID world, and make yourself a cup of coffee while Nx does its thing.

A developer drinking coffee waiting for the Nx command to finish

Now, let's get into the directory that Nx has created for us and list all the projects that have been generated:

cd debugging-example
npx nx show projects

And the response:

debugging-example-e2e
debugging-example

Sweet, we have created our Angular app and our Playwright end-to-end testing project!

Let's run our tests and verify that the scaffolding worked and all of our tests are passing:

npx nx run-many -t test e2e

And the response:

  nx run debugging-example:test (10s)
  nx run debugging-example-e2e:e2e (2m)

WHOO HOO! A great success! We have set up our project as an Nx Monorepo, and all the tests pass. You are a Golden God!

A Happy Developer giving a thumbs up

Let's get to Debugging🔗

Let's open VS Code in our project root folder of debugging-example with the following command:

code .

Navigate to the apps\debugging-example\src\app\app.component.spec.ts our generated test file that we want to debug.

1import { TestBed } from '@angular/core/testing';
2import { AppComponent } from './app.component';
3import { NxWelcomeComponent } from './nx-welcome.component';
4import { RouterTestingModule } from '@angular/router/testing';
5
6describe('AppComponent', () => {
7  beforeEach(async () => {
8    await TestBed.configureTestingModule({
9      imports: [AppComponent, NxWelcomeComponent, RouterTestingModule],
10    }).compileComponents();
11  });
12
13  it('should render title', () => {
14    const fixture = TestBed.createComponent(AppComponent);
15    fixture.detectChanges();
16    const compiled = fixture.nativeElement as HTMLElement;
17    expect(compiled.querySelector('h1')?.textContent).toContain(
18      'Welcome debugging-example'
19    );
20  });
21
22  it(`should have as title 'debugging-example'`, () => {
23    const fixture = TestBed.createComponent(AppComponent);
24    const app = fixture.componentInstance;
25    expect(app.title).toEqual('debugging-example');
26  });
27});

In VS code, click in the gutter to the left of line number 17 to create a breakpoint for debugging purposes.

Setting a breakpoint on line 17

A breakpoint for those who do not know is how you can tell your program to stop executing and pause here so that you can inspect all of the runtime values of your code at that location in a source file inside your IDE. It's way better than writing a ton of console.log() statements.

Let's run the jest tests just for the debugging-example project in our Nx monorepo and see if we hit our breakpoint.

In VS Code, open your terminal with Ctrl ` on Windows or ⌘ ` on a Mac to open the integrated terminal and enter the following command:

npx nx test debugging-example

And the output:

PASS   debugging-example  apps/debugging-example/src/app/app.component.spec.ts (5.258 s)
  AppComponent
     should render title (170 ms)
     should have as title 'debugging-example' (28 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.485 s
Ran all test suites.

Unfortunately, VS Code did not stop at our breakpoint on line 17.

A sad developer because our breakpoint did not hit

Let's get Debugging to actually work in a Jest Test🔗

Okay, here is the secret sauce to get the debugging to work auto-magically for our future selves when we need to debug a Jest unit test!

Open the command palette in VS code with Ctrl Shft P or in Mac speak hit ⌃ ⇧ P and type in >Debug: JavaScript Debug Terminal and hit . Showing the JavaScript Debug Terminal command

You will notice that VS Code will add a new special terminal for you: Showing the JavaScript Debug Terminal Window

In that new JavaScript Debug Terminal let's try our Nx test command again:

npx nx test debugging-example

It still didn't work. Why?!

The Developer still sad because the breakpoint still did not hit

Well, we need to watch for changes to the files for the debugger to be able to have enough time to attach, so let's try this modified command:

npx nx test debugging-example --watch

And the output:

No tests found related to files changed since last commit.
Press `a` to run all tests, or run Jest with `--watchAll`.

Watch Usage
  Press a to run all tests.
  Press f to run only failed tests.
  Press p to filter by a filename regex pattern.
  Press t to filter by a test name regex pattern.
  Press q to quit watch mode.
  Press Enter to trigger a test run.

I chose to press a to run all tests, but you can use any of the other options to limit the number of tests that will be executed.

Notice the --watch argument to our test command? That is the magic sauce.

An image showing we hit the breakpoint!

Success! We have hit our breakpoint, and we can now use VS Code to debug our Jest Tests!

A Developer that is happy because we finally hit the breakpoint to debug our test

TL;DR🔗

  1. Use the command palette and search for Debug: JavaScript Debug Terminal to open a special terminal window in VS Code
  2. Execute your Nx test commands passing in the --watch argument so that the debugger has the opportunity to attach and create the debugging session
  3. Profit?

Conclusion🔗

I hope this article has helped you out and if you have any questions feel free to join my discord server and ask away!

I also stream on twitch, sometimes I'm coding to the void, other times I am gaming to the void!