Debugging Javascript Unit Tests with VSCode

Featured on Hashnode

Do you just use console.log for everything?

When you run code locally, sometimes the easiest way to get the current value of something is console.log to write to a terminal. This is easy. But sometimes, you need more insight into what a variable is really set to, and how it's set. Your editor's debugger is suitable for this.

Setting up VSCode's debugger to work with Javascript was once cumbersome. I would simply avoid it because I would waste as much time getting it to work as it would save me. Today, it's very simple. Now it is very easy for me to test breakpoints, and step through execution line by line. This is very handy in tricky situations where console.log is a mess.

And it works very well with unit tests, trying to understand why a particular test just won't pass. In this example, we'll look at tests written with both mocha and jest.

Enable the debugger

In VSCode, open the command palette, and search for "JavaScript Debug Terminal"

Click that, and it will open a terminal that will automatically connect to the process with the debugger.

Set a breakpoint in your code:

The red dot indicates a breakpoint

The red dot indicates a breakpoint.

Then, run your code normally with npm start or npm run test.

That's it! It's that easy.

Now what

Execution should stop on your breakpoint, and controls like "Step over", "Step in", and "Step out" should work.

  • Step over - advance over this line and pause on the next line after

  • Step in - execute the current line, and if it calls a function, pause at the beginning of the function it steps into

  • Step out - execute the current line and the rest of the current function, pausing at the next line in whatever context called the current function.

  • And of course, it will always stop at breakpoints that you set explicitly.

In practice, stepping in/out/over doesn't always do what I expect, but it does work reasonably well, and it works great with await lines these days.

Watch expressions are really handy:

Add a watch expression in the debug "watch" pane and you can see the current value of whatever expression you put in the box when execution pauses.

I thought it would be harder

There was a time when getting this to work would involve specific settings in your launch.json or settings.json file, and then you have to decide to commit those files, and decide whether the whole team should be locked into VSCode. This zero-config functionality is much better.

A caveat about node_modules

You might find that "Step in" will step all the way into 3rd party node modules - not your code - and Node.js internals - also not your code. Chances are you don't want to debug that. So, add this to a .vscode/settings.json in your project.

{
  "debug.javascript.terminalOptions": {
    "skipFiles": [
      "${workspaceFolder}/node_modules/**/*",
      "<node_internals>/**"
    ]
  }
}

So that's something you do need to commit and share, but it's not much! And you don't need it to get started.

Unit testing with mocha and jest

I said that this works for unit tests. There are caveats.

First, let's talk about mocha and jest

I won't dig into all the pros and cons or their alternatives, but these are the two most popular testing frameworks I know of for Node.js. They are very similar. There are differences.

Both mocha and jest:

  • Define tests with global describe and it functions. These functions are almost the same between the two.

  • Work with Typescript / ts-node

  • Configurable

  • Can run with parallelism

  • Can run in "watch" mode

mocha:

  • Single process by default. Can be configured for multiple processes, there are reasons you might not want to.

  • Formatters are available as plugins. It can generate output in lots of flavors.

  • mocks are not built in. You'll need a separate library. typemoq is handy.

  • assertions are not built in. You'll need chai or should

  • Code coverage is not built in. Use nyc instead.

jest:

  • Runs parallel tests by default

  • mocks are built in. But I also like to use jest-mock-extended.

  • assertions are built in to a global expect function

  • Code coverage is built in with the collectCoverage option.

I tend to like jest better, but after writing this doc, I've softened on mocha and TypeMoq a bit.

Ok, what caveats

Tests will time out

Both mocha and jest have a default timeout for tests upon which they will fail. You probably want to take longer to debug. You will want to increase their configured timeout so you can take time to step through execution.

For this, I added a separate script to my package.json with a :debug suffix. Here are examples for mocha and jest:

    "test:mocha:debug": "nyc mocha --timeout 300000",
    "test:jest:debug": "jest --testTimeout 300000"

Parallelism can bite you

If you normally have tests configured to run in parallel, which makes for faster CI builds, you might want to disable that for debugging. Jest and mocha will run each test suite (file) in a separate process, and there's no real guarantee that your debugger will be attached to the correct process.

I did experience some wonkiness when I wrote this up, but I couldn't reproduce the issue reliably enough to tell you how to make it happen. If it happens, then you probably want to try this:

    "test:mocha:debug": "nyc mocha --timeout 300000 --jobs 1",
    "test:jest:debug": "jest --testTimeout 300000 --maxWorkers 1"

Try it out

Clone this repo.

Run npm run test:mocha:debug to see the output with mocha. Can you see the line that isn't covered and the failing test? Can you fix them?

Run npm run test:jest:debug to see the output with jest. Can you see the line that isn't covered and the failing test? Can you fix them?

You're welcome to try out this example safely in a dev container.

Conclusion

  • You can use the debugger with zero config

  • This is great for when unit tests are hard to debug

  • There are caveats with unit tests around timeouts and parallelism

  • So, zero-config gets you started, but a little extra config gets you the rest of the way

Any additional tips and tricks? Let me know if there's a way to improve this.