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
Reporterinterface. - 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.jsonand 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, andonRunComplete. - Consider how to efficiently gather and store test results before writing the final JSON file. The
onRunCompletemethod is a good place to perform the final write operation. - You can configure Jest to use your custom reporter by adding a
reportersarray to yourjest.config.js(orjest.config.ts) file. - Think about how to handle asynchronous operations, especially when writing the final JSON file.