Hone logo
Hone
Problems

Jest JSON Reporter: Custom Test Result Output

This challenge involves creating a custom Jest reporter that outputs test results in a structured JSON format. This is useful for integrating Jest test results with other CI/CD tools, dashboards, or for custom analysis of your test suite's performance.

Problem Description

Your task is to implement a custom Jest reporter in TypeScript that transforms Jest's test results into a well-defined JSON structure. This reporter should capture essential information about each test run, including the overall status, individual test case results (pass, fail, skip), and any associated error messages or stack traces.

Key Requirements:

  • Create a class that extends Jest's Reporter interface.
  • Implement the necessary methods to capture test results as they occur during a Jest run.
  • The reporter should generate a single JSON file containing the aggregated test results.
  • The JSON output should be human-readable and programmatically parseable.

Expected Behavior:

When Jest runs with your custom reporter enabled, it should produce a JSON file at a specified location. This file will contain an object with the following structure:

{
  "summary": {
    "startTime": "ISO_STRING",
    "endTime": "ISO_STRING",
    "duration": "NUMBER_IN_MS",
    "testResults": {
      "suites": "NUMBER",
      "tests": "NUMBER",
      "passes": "NUMBER",
      "fails": "NUMBER",
      "pending": "NUMBER",
      "skipped": "NUMBER",
      "todo": "NUMBER"
    }
  },
  "suites": [
    {
      "filePath": "STRING",
      "testResults": [
        {
          "title": "STRING",
          "status": "PASS" | "FAIL" | "PENDING" | "SKIPPED" | "TODO",
          "duration": "NUMBER_IN_MS" | null,
          "failureMessages": ["STRING"] | null,
          "ancestorTitles": ["STRING"]
        }
      ]
    }
  ]
}

Edge Cases to Consider:

  • Tests that are skipped or marked as todo.
  • Tests that throw uncaught exceptions.
  • Empty test suites.
  • Tests with multiple assertions that might fail.

Examples

Example 1: Simple Passing Test

Let's assume you have a math.test.ts file with a single passing test:

// math.test.ts
describe('Math operations', () => {
  test('should add two numbers', () => {
    expect(2 + 2).toBe(4);
  });
});

Input to Reporter: Jest's internal events corresponding to the execution of math.test.ts.

Output:

{
  "summary": {
    "startTime": "2023-10-27T10:00:00.000Z", // Actual start time
    "endTime": "2023-10-27T10:00:00.123Z",   // Actual end time
    "duration": 123,                       // Actual duration
    "testResults": {
      "suites": 1,
      "tests": 1,
      "passes": 1,
      "fails": 0,
      "pending": 0,
      "skipped": 0,
      "todo": 0
    }
  },
  "suites": [
    {
      "filePath": "math.test.ts",
      "testResults": [
        {
          "title": "should add two numbers",
          "status": "PASS",
          "duration": 10, // Actual test duration
          "failureMessages": null,
          "ancestorTitles": ["Math operations"]
        }
      ]
    }
  ]
}

Example 2: Failing Test

Consider a string.test.ts file with a failing test:

// string.test.ts
describe('String manipulation', () => {
  test('should reverse a string', () => {
    expect('hello').toBe('olleh'); // This will fail
  });
});

Input to Reporter: Jest's internal events for string.test.ts.

Output:

{
  "summary": {
    "startTime": "2023-10-27T10:01:00.000Z",
    "endTime": "2023-10-27T10:01:00.250Z",
    "duration": 250,
    "testResults": {
      "suites": 1,
      "tests": 1,
      "passes": 0,
      "fails": 1,
      "pending": 0,
      "skipped": 0,
      "todo": 0
    }
  },
  "suites": [
    {
      "filePath": "string.test.ts",
      "testResults": [
        {
          "title": "should reverse a string",
          "status": "FAIL",
          "duration": 20,
          "failureMessages": [
            "Expected: \"olleh\"\nReceived: \"hello\""
          ],
          "ancestorTitles": ["String manipulation"]
        }
      ]
    }
  ]
}

Example 3: Skipped and TODO Tests

Consider a feature.test.ts file:

// feature.test.ts
describe('New Feature', () => {
  test.skip('should be functional', () => {
    expect(true).toBe(true);
  });

  test.todo('should handle edge cases');
});

Input to Reporter: Jest's internal events for feature.test.ts.

Output:

{
  "summary": {
    "startTime": "2023-10-27T10:02:00.000Z",
    "endTime": "2023-10-27T10:02:00.100Z",
    "duration": 100,
    "testResults": {
      "suites": 1,
      "tests": 2,
      "passes": 0,
      "fails": 0,
      "pending": 0,
      "skipped": 1,
      "todo": 1
    }
  },
  "suites": [
    {
      "filePath": "feature.test.ts",
      "testResults": [
        {
          "title": "should be functional",
          "status": "SKIPPED",
          "duration": null, // Skipped tests often have no duration recorded
          "failureMessages": null,
          "ancestorTitles": ["New Feature"]
        },
        {
          "title": "should handle edge cases",
          "status": "TODO",
          "duration": null,
          "failureMessages": null,
          "ancestorTitles": ["New Feature"]
        }
      ]
    }
  ]
}

Constraints

  • The reporter should be implemented in TypeScript.
  • The output JSON file should be named jest-results.json and saved in the root directory of the project (or a configurable path).
  • The reporter should not significantly impact Jest's overall execution time (e.g., avoid heavy synchronous file I/O for every test event).
  • The JSON structure must adhere to the format described in the "Expected Behavior" section.

Notes

  • Familiarize yourself with Jest's custom reporter API. You'll likely need to implement methods like onRunStart, onSuiteStart, onTestResult, and onRunComplete.
  • Consider how to efficiently gather and store test results before writing the final JSON file. The onRunComplete method is a good place to perform the final write operation.
  • You can configure Jest to use your custom reporter by adding a reporters array to your jest.config.js (or jest.config.ts) file.
  • Think about how to handle asynchronous operations, especially when writing the final JSON file.
Loading editor...
typescript