unit-testing-licenses
Web Development |

Unit Testing Licenses: Monitoring the legality of your node_modules with Jest

Moritz Jacobs

March 23, 2023

tl;dr quick summary
It is important to keep track of the open source licenses you use in your JavaScript projects, not every license is suitable for every use case. What if we address this by making your node_module's licenses part of the test suite?

Packages from the npm registry are the heart and soul of most larger JavaScript projects. Libraries, frameworks, tooling, etc. - all of our projects stand on the shoulders of giants open source projects. Writing and maintaining all that code in-house would be impossible, even for the biggest companies out there. But there is a price to pay, and supply chain attacks are just one of the risks of using third-party code. In a commercial context, you must always be aware of the legal implications of open source licenses. There are a myriad of different licenses, and they all allow different kinds of use. I will explicitly not go into the details (I am not a lawyer, you should always consult legal professionals for these matters), but it is important to keep track of the licenses you use in your projects. Not every open source license is suitable for every use case, and we have to be aware of this!

There are 3 main concerns we need to deal with:

  • We want to allow our clients (or their legal team) to have an overview of all the licenses we use in their product (often a contractual obligation).
  • We want to avoid certain licenses, so we need to prevent developers from unknowingly adding them.
  • We want to know if a package has changed its license for some reason, and then check if the new license is still allowed.

Today I want to show how we can address these problems by making licenses part of the test suite. This test suite could be run on every proposed change to the codebase (e.g. as a GitHub action on every Pull Request), so that an unwanted change in licensing will cause our CI to fail early.

Example project

Let's say we have a TypeScript project that already has a few unit tests in place. It uses Jest / ts-jest for that, but you can probably adapt the idea for any other test framework. If you're new to Jest, they have an excellent setup guide. ts-jest also has you covered if you want to use TypeScript.

To easily compile the information we want, we are going to use license-checker-rseidelsohn, which gathers the packages from our package file and then extracts license and author information from node_modules. It's then able to generate a list in multiple formats -- in our case, markdown. This package is also handy because it is able to provide a CLI as well as exports for programmatic use. Here's what a CLI run could look like (--direct means: only include direct dependencies, not their transitive dependencies):

npx license-checker-rseidelsohn --markdown --direct

Screenshot of the result of the above command: Markdown text, listing all of the dependencies (and their licenses) of an example projecct

We can now write the result to a markdown file (→ npx license-checker-rseidelsohn --markdown > LICENSES.md). If this file is in the repo of our project, GitHub will render pretty HTML for us. For 99% of our projects this will solve use case #1, our client's legal team gets access to that GitHub URL and we're done. To keep the file up to date we can also use a GitHub action like so:

This will open a PR when the LICENSES.md needs to change.

That was easy. Now to the fun part:

Unit testing against legality

Let's put all of the other checks inside a new unit test, let's call it licenses.test.ts (again, we're using TypeScript, which is not necessary for this, if you don't want to). In this test we need to do a few things:

First, let's import the checker and promisify it, so we can use async/await later:

The result from the checker is an object with a packageName@version as its keys and some more information as its values:

Since we don't need all of it, yet the version number is in the key, we should probably tidy it up before we use it. Let's do all of that in beforeAll:

As a first test, let's find out if any current packages use disallowed licenses. As an example, I will only allow ["UNLICENSED", "MIT", "BSD-3-Clause", "Apache-2.0"]. The actual test uses a custom matcher, so we can control the output of failing test messages better. We also need to tell TypeScript what to expect from our custom matcher.

Now the first test is very simple:

This test will run every package against our custom matcher and fail, if its license(s) is not in the allow list. This solves our use case #2.

Now for our second test, we want to future-proof the set of different licenses in the project in order to detect if a package was added, removed or had its license changed. We can do this using a snapshot test:

For those unaware of what a snapshot test is: when it is executed for the first time, it will save a current snapshot of the result to a file __snapshots__/licenses.test.ts.snap. This file must then be checked into the repository. Everytime the test runs in the future, its result will be compared to that snapshot. If it changed, the test will fail and Jest will tell you.

Alright, let's run our tests: → npm run test

Screenshot of the CLI output of the above command: all tests passed and Jest tells us, it wrote a snapshot from 1 test suite

Everything seems good, all our packages honor our LICENSE_ALLOW_LIST and we now have a snapshot file, that we can commit:

Let's see what happens when we add a dependency: → npm install @react-hookz/webnpm run test

Screenshot of another test run after adding a dependency: the first test passes, the snapshot test fails.

From this output we can see, that @react-hookz/web's license is allowed (the first test passed!) but it's not in the snapshot yet. Let's fix that by running npm run test -- -u:

Screenshot of Jest's CLI output: both tests pass again, the snapshot was updated.

Let's add another dependency: → npm install rimrafnpm run test.:

Screenshot of CLI output: both tests fail:'

Uh oh! rimraf uses the ISC license, which is not in our allow list and our first test caught that: "ISC" is not an allowed license (dependency: rimraf). We can now decide if we want to remove this package and use something else or change the allow list of the test.

Summary:

This should do the trick! All 3 use cases are covered by a few lines of TypeScript and YAML. You can check out a fully working example repo (including GitHub actions) here: https://github.com/peerigon/blog-license-check-demo

If you only want the test file, here you go:

open source licenses

unit testing

node_modules

Read also

Francesca, Ricarda, 11/21/2024

Top 10 Mistakes to Avoid When Building a Digital Product

MVP development

UX/UI design

product vision

agile process

user engagement

product development

Go to Blogarticle

Leonhard, 10/22/2024

Strategies to Quickly Explore a New Codebase

Web App Development

Consulting

Audit

Go to Blogarticle
A castle-like building behind a half-open gate

Leonhard, 07/15/2024

User Input Considered Harmful

TypeScript

Web App Development

Best Practices

Full-Stack

Validation

Go to Blogarticle