avatarChanon Roy

Summary

The provided content outlines the process of setting up a custom test reporter for Jest using Typescript, detailing the steps to integrate it with a Javascript project for enhanced debugging and visibility into test performance.

Abstract

The article titled "How to setup a custom test reporter for Jest" explains the benefits of using Jest's test reporter feature to gain insights into runtime performance and test failures. It guides developers through the initial setup of Jest and Typescript in a project, the configuration of the Jest reporter to point to a custom Typescript reporter, and the creation of the reporter itself. The article emphasizes the ease of creating a custom reporter by providing a simple example that logs test results. It also discusses the potential to enhance the reporter by utilizing additional lifecycle methods provided by @jest/reporters and accessing Jest configuration values. The article concludes by suggesting various ways to utilize the test results, such as persisting them as artifacts, integrating with observability platforms, or setting up notifications via communication tools like Slack or GitHub.

Opinions

  • The author believes that creating a custom test reporter is straightforward and can significantly improve the debugging process by providing visibility into test execution details.
  • Typescript is recommended for writing the custom reporter to leverage its strong type support and make the reporter more robust.
  • The article suggests that using custom options and lifecycle methods can make the reporter more intelligent and tailored to specific environments, such as Continuous Integration (CI) pipelines.
  • There is an emphasis on the usefulness of the testResults array, which contains detailed metadata about test outcomes, including failure messages and open handle detection for identifying flaky tests.
  • The author encourages experimentation with persisting test results in various formats and integrating with third-party services, highlighting the flexibility and potential of a custom Jest reporter.

How to setup a custom test reporter for Jest

As one of the most popular test runners in the Javascript ecosystem, Jest gives us a variety of helpful tools to help us test our code. The test reporter is one of these features.

With a few lines of code, we can effortlessly create visibility into runtime performance and test failures — both of which are super helpful for debugging flakey tests. As an added bonus, we can also make our test reporter more robust by writing in Typescript.

📦 Setting up Jest in our project

To begin, we’ll install some dependencies and setup Typescript.

> yarn init
> yarn add typescript jest ts-node @jest/reporters @jest/types -D
> yarn tsc --init

After setting up our Typescript project, we need to add a new property called reporters to our Jest configuration. This property accepts an array that we will use to point to our custom reporter.

Note: You can have many reporters, but we’re just creating one.

// jest.config.js

module.exports = {
  reporters: ["<rootDir>/test-reporter/index.js"],
};

We can then create a new folder called test-reporter and create two files:

  • index.js — our entry point that we can use to compile Typescript
  • test-reporter.ts — our custom reporter written in Typescript
> mkdir test-reporter
> cd test-reporter
> touch index.js
> touch test-reporter.ts

In index.js, we can configure our entry point file to compile our reporter module to Typescript at runtime when we run our tests.

// test-reporter/index.js

const tsNode = require("ts-node");

tsNode.register({
  transpileOnly: true,
  compilerOptions: require("../tsconfig.json").compilerOptions,
});

module.exports = require("./test-reporter");

📝 Creating our Custom Reporter

With Typescript set to compile at runtime, we can now write our custom test reporter. Here is a simple test reporter that logs test results when the test runner finishes:

import { Reporter, TestContext } from "@jest/reporters";
import { AggregatedResult } from "@jest/test-result";

type CustomReporter = Pick<Reporter, "onRunComplete">;

export default class TestReporter implements CustomReporter {
  constructor() {}

  onRunComplete(_: Set<TestContext>, results: AggregatedResult) {
    console.log(results);
  }
}

From results, we receive an aggregate set of test results:

{
  numFailedTestSuites: 0,
  numFailedTests: 0,
  numPassedTestSuites: 0,
  numPassedTests: 0,
  numPendingTestSuites: 0,
  numPendingTests: 0,
  numRuntimeErrorTestSuites: 0,
  numTodoTests: 0,
  numTotalTestSuites: 2,
  numTotalTests: 0,
  openHandles: [],
  snapshot: {
    added: 0,
    didUpdate: false,
    failure: false,
    filesAdded: 0,
    filesRemoved: 0,
    filesRemovedList: [],
    filesUnmatched: 0,
    filesUpdated: 0,
    matched: 0,
    total: 0,
    unchecked: 0,
    uncheckedKeysByFile: [],
    unmatched: 0,
    updated: 0
  },
  startTime: 1652692338326,
  success: false,
  testResults: [],
  wasInterrupted: false
}

A really useful property here is testResults, which is an array of testResult objects. These contain really useful metadata such as failure messages and open handle detection (useful for flakey tests).

⚙️ Enhancing our Custom Reporter

Thanks to tools given to us by @jest/reporters, we can improve upon our custom reporter in many ways.

First, we can hook into additional lifecycle methods beyond just onRunComplete, such as: onTestResult, onRunStart, onTestStart, onRunComplete, and even getLastError. With Typescript configured, we can ensure these methods have strong type support as well.

Secondly, we can make our reporter more intelligent about when it should run by being able to read our Jest configuration values from the reporter.

import { Reporter, TestContext } from "@jest/reporters";
import { AggregatedResult } from "@jest/test-result";
import { Config } from "@jest/types";

type CustomReporter = Pick<Reporter, "onRunComplete">;

export default class TestReporter implements CustomReporter
{
  // Add the config to our constructor
  constructor(private config: Config.InitialOptions) {}

  onRunComplete(context: Set<TestContext>, results: AggregatedResult) {
    const isCi = this.config.ci

    // Only run in a CI environment    
    if (isCi) {
      console.log(results);
    }
  }
}

We can also pass in custom options in our Jest configuration.

// jest.config.js

module.exports = {
  reporters: [["<rootDir>/test-reporter/index.js", { useReporter: true }]],
};

And then access these custom values inside our reporter.

import { Reporter, TestContext } from "@jest/reporters";
import { AggregatedResult } from "@jest/test-result";
import { Config } from "@jest/types";

type CustomReporter = Pick<Reporter, "onRunComplete">;

interface Options {
  useReporter: boolean;
}

export default class TestReporter implements CustomReporter {
  constructor(
    private config: Config.InitialOptions,
    private options: Options
  ) {}

  onRunComplete(context: Set<TestContext>, results: AggregatedResult) {
    const isCi = this.config.ci;
    const useReporter = this.options.useReporter;

    if (isCi || useReporter) {
      console.log(results);
    }
  }
}

🤔 What should we do with the results?

With an intelligent test reporter at the ready, it would be great to persist our results somewhere. Fortunately, we can do many things here.

  • You could persist them as text files and save them as artifacts in your CI environment.
  • You could push them to a third-party observability platform, such as Datadog.
  • You could write an elaborate bot to ping you on Slack or GitHub.

The choice is yours! 🚀

For more information on Jest reporters, visit the docs or use explore my working code sample here.

Jest
JavaScript
Testing
Programming
Software Engineering
Recommended from ReadMedium