Skip to main content

Testing and Debugging

Welcome to the guide on testing and debugging your Projen projects! If you're new to Projen or testing in general, don't worry — this page is designed to help you understand and effectively use testing and debugging techniques in your projects.

Testing

In Projen projects, the most common way to run tests is using a testing framework called Jest. Jest is a JavaScript Testing Framework with a focus on simplicity.

When you set up your Projen project, you can configure Jest in your projenrc.js file like this:

const project = new NodeProject({
// ...
jestOptions: {
jestConfig: {
testMatch: ["**/*.test.ts"],
},
},
});

However, most of the time, the default Jest configuration works fine.

jest or any other testing framework can be used to test projen projects. The following sections will describe some of the common testing patterns in jest. Feel free to contribute best practices and tips for other testing frameworks.

Snapshot testing

The most straightforward method for testing projen projects is to use jest's snapshot testing feature. This feature allows you to take a snapshot of the project's generated files and compare them to the expected output.

Here's a basic example:

import { Testing } from "projen/lib/testing";
import { MyProject } from "../src/my-project";

test("snapshot", () => {
const project = new MyProject();
const snapshot = Testing.synth(project);
expect(snapshot).toMatchSnapshot();
});

The above test will generate a snapshot of the entire project and compare it to the expected output. If the snapshot does not match, the test will fail.

You'll quickly find that these types of snapshot tests are unwieldy - it captures the entire project and it's hard to pinpoint exactly what changed. However, you can adjust the test to look at the contents of a single file that was generated:

import { Testing } from "projen/lib/testing";
import { MyProject } from "../src/my-project";

test("snapshot", () => {
const project = new MyProject();
const snapshot = Testing.synth(project);
expect(snapshot["README.md"]).toMatchSnapshot();
});

This test will only compare the contents of the README.md file to the expected output. In other words, your snapshot file will only contain the contents of the README.md file and not the entire project tree.

note

Generally, if you just want to make sure that the contents of a single file are consistent, favor snapshot testing over comparing the contents directly.

Testing file creation

Testing.synth() returns a map of all the files that were generated by the project. You can use this map to identify whether specific files were created.

import { Testing } from "projen/lib/testing";
import { MyProject } from "../src/my-project";

test("snapshot", () => {
const project = new MyProject();
const snapshot = Testing.synth(project);
expect(snapshot[".github/workflows/release.yml"]).toBeDefined();
});
info

Note that this type of testing only identifies whether the file exists. It does not do any content comparison. You likely will want to use this type of testing in conjunction with snapshot testing.

Debugging

Unit testing is great for testing individual components of your project, but sometimes it's easier to actually build the project locally and debug it.

Debugging with yarn

If you're using yarn, you can use the yarn link command to build your project locally and link it to your project. For example, if you have a projen project in ~/my-project and you want to debug it in ~/my-app, you can run the following commands:

# In ~/my-project
yarn link

# In ~/my-app
yarn link my-project
yarn projen new my-project --from my-project

Assuming my-project is the name of your project in ~/my-project/projenrc.js.

For more information, see the yarn documentation.

Debugging with npm

If you prefer NPM, the process is similar:

# In ~/my-project
npm link

# In ~/my-app
npm link my-project

npx projen new my-project --from my-project

Debugging with pnpm

For PNPM users, the steps are also quite straightforward:

# In ~/my-project
pnpm link

# In ~/my-app
pnpm link my-project
npx projen new my-project --from my-project

Common Issues and Solutions

Non-Deterministic Snapshot Tests

Snapshot tests should be predictable, but sometimes they fail randomly. This can happen if your project attempts to modify a file that's already in your project tree.

For example, if your project is trying to create a README.md file, but the project already has a README.md file, you either run into a race condition or one file will overwrite the other consistently. This is especially common when extending existing projen projects.

Resolving this issue can be challenging. If you are extending an existing projen project, you can try removing your README.md file code and see if it still exists in the project tree with a snapshot test. If it does, then something else in your project code is already creating the file, and you'll need to identify what that is and either remove it or override it.

Example solution

In the example below, we're extending the NodeProject class, which already creates a README.md file. In fact, all classes that extend GitHubProject will create a README.md file by default. This behavior is controlled by the readme option in the GitHubProjectOptions interface.

We can remove the README.md file from the base project, which should allow the one we've hypothetically added in CustomNodeProject to be created.

const project = new CustomNodeProject({
// ...
readme: false, // This will disable the readme file in the underlying NodeProject
});

project.synth();

Alternately, since we know that the README.md file is already being created, we can remove the code that creates it in CustomNodeProject and just use the one from the base class.

For classes that do not have a readme option, you can use the tryRemoveFile option to remove the file from the project tree.

const project = new CustomNodeProject({
// ...
});
project.tryRemoveFile("README.md");

Note, however, that this will probably also remove the README.md file created from CustomNodeProject, so in this particular case, it's probably better to just disable the README.md file in the base class or use it instead of a custom implementation.