Skip to content
Colin Wren
Twitter

Writing a Jest Test Reporter

JavaScript, Testing, Software Development3 min read

Recently at work I’ve been building a means to upload the test run data for our end to end tests into Jira via Zephyr.

Normally the approach to integrating test run results is to export them as JUnit compatible documents and upload them into a tool that supports that format.

Zephyr has an API and one of the other teams had built a library for interacting with it, so I decided to utilise this to upload the results at the end of the test runs.

One of the benefits of using Zephyr like this is that I can get the environment variables and use this as part of the test run metadata to help track each test run in each environment.

What is a test reporter?

writing report
Photo by rawpixel on Unsplash

A test reporter is a hook into the test runner that allows for code to be executed at the start and end of the test run.

There are many test reporters available for Jest; ones that handle different data formats, integrate with different systems or just help with notifications.

To use a test reporter you first install the package via npm install and then in your jest.config.js you add it’s name to the reports array.

1module.exports = {
2 testEnvironment: 'node',
3 silent: false,
4 reporters: [
5 'default',
6 'couchbaseReporter'
7 ]
8};
Example jest.config.js to include a customer reporter (in this case it upserts test run data to Couchbase)

Stages the test reporter can hook into

hooks
Photo by chuttersnap on Unsplash

There are two events the test reporter hooks into:

onRunStart This stage is before the test run starts but the number of test suites ( describe blocks) has been calculated so you can use this value to create placeholders for the results if you want to.

onTestResult This stage is fired off at the end of the test suite and can be used to process the results of the test suite as well as update progress on the test run itself.

If you’re looking to visualise test progress as the test run is taking place this will be the best hook to use.

onRunComplete This stage is fired off afterwards and can be used to process the test results after all tests have run (or in the event of the test-run being aborted due to an error).

If you’re looking to upload the results of all the tests in bulk this would be the best hook to use.

Data provided at the various stages

There isn’t too much documentation I’ve found around the data that’s passed to each stage so I decided to run the test from https://gitlab.com/colinfwren/containerised-jest and collect the arguments passed at each stage.

The output of the process will be a JavaScript object with each positional argument being represented by its index in the function’s signature. The data passed for that argument is the value for that key.

I’ve created a custom Jest reporter to help document the data received as part of the test run life cycle. This includes a JSDoc that contains the data structure also.

onRunStart

As there’s not much data being passed at this stage it’s common to see the first argument deconstructed to collect the numTotalTestSuites value such as:

1onRunStart({ numTotalTestSuites }) {
2 // do some stuff
3}
1{
2 '0': {
3 numFailedTestSuites: 0,
4 numFailedTests: 0,
5 numPassedTestSuites: 0,
6 numPassedTests: 0,
7 numPendingTestSuites: 0,
8 numPendingTests: 0,
9 numRuntimeErrorTestSuites: 0,
10 numTodoTests: 0,
11 numTotalTestSuites: 15,
12 numTotalTests: 0,
13 openHandles: [],
14 snapshot: {
15 added: 0,
16 didUpdate: false,
17 failure: false,
18 filesAdded: 0,
19 filesRemoved: 0,
20 filesUnmatched: 0,
21 filesUpdated: 0,
22 matched: 0,
23 total: 0,
24 unchecked: 0,
25 uncheckedKeysByFile: [],
26 unmatched: 0,
27 updated: 0
28 },
29 startTime: 1552149691390,
30 success: false,
31 testResults: [],
32 wasInterrupted: false
33 },
34 '1': {
35 estimatedTime: 0,
36 showStatus: true
37 }
38}
The arguments onRunStart is called with

onTestResult

The first argument passed provides context around the test being executed, such as the config for the test runner and the package it’s being run as part of.

The second argument passed provides information on the test suite & tests executed such as:

  • How many tests passed in the test suite
  • How many tests failed in the test suite
  • Time taken for all tests in the test suite
  • File path for the test suite
  • Snapshot changes for the test run
  • Name of the test
  • Duration of the test
  • Ancestors of the test (useful for nested describe blocks)
  • Failure messages for the test if it failed (useful for leaving stack traces as comments on failed tests)

The third argument passed provides information on the complete test run at that point in time such as total number of tests run so far, total number of passed/failed tests and passed/failed test suites. This object also contains the test suite data passed as the second argument.

1{
2 '0': {
3 context: {
4 config: {
5 automock: false,
6 browser: false,
7 cache: true,
8 cacheDirectory: '/private/var/folders/_x/_49pjjwx42b4t1j9v9vjv4yh0000gn/T/jest_dx',
9 clearMocks: false,
10 coveragePathIgnorePatterns: [
11 '/node_modules/'
12 ],
13 cwd: 'containerised-jest',
14 dependencyExtractor: null,
15 detectLeaks: false,
16 detectOpenHandles: false,
17 errorOnDeprecated: false,
18 filter: null,
19 forceCoverageMatch: [],
20 globalSetup: null,
21 globalTeardown: null,
22 globals: {},
23 haste: {
24 computeSha1: false,
25 providesModuleNodeModules: []
26 },
27 moduleDirectories: [
28 'node_modules'
29 ],
30 moduleFileExtensions: [
31 'js',
32 'json',
33 'jsx',
34 'ts',
35 'tsx',
36 'node'
37 ],
38 moduleNameMapper: {},
39 modulePathIgnorePatterns: [],
40 name: '3471fa8936fa10d76fe56180a37ce8ea',
41 prettierPath: 'prettier',
42 resetMocks: false,
43 resetModules: false,
44 resolver: null,
45 restoreMocks: false,
46 rootDir: 'containerised-jest',
47 roots: [
48 'containerised-jest'
49 ],
50 runner: 'jest-runner',
51 setupFiles: [],
52 setupFilesAfterEnv: [],
53 skipFilter: false,
54 snapshotSerializers: [],
55 testEnvironment: 'containerised-jest/node_modules/jest-environment-node/build/index.js',
56 testEnvironmentOptions: {},
57 testLocationInResults: false,
58 testMatch: [
59 '**/__tests__/**/*.[jt]s?(x)',
60 '**/?(*.)+(spec|test).[tj]s?(x)'
61 ],
62 testPathIgnorePatterns: [
63 '/node_modules/'
64 ],
65 testRegex: [],
66 testRunner: 'containerised-jest/node_modules/jest-jasmine2/build/index.js',
67 testURL: 'http://localhost',
68 timers: 'real',
69 transform: [
70 [
71 '^.+\\.[jt]sx?$',
72 'node_modules/babel-jest/build/index.js'
73 ]
74 ],
75 transformIgnorePatterns: [
76 '/node_modules/'
77 ],
78 watchPathIgnorePatterns: []
79 },
80 hasteFS: {
81 _rootDir: 'containerised-jest',
82 _files: {}
83 },
84 moduleMap: {
85 duplicates: [],
86 map: [
87 [
88 'containerised-jest',
89 {
90 g: [
91 'package.json',
92 1
93 ]
94 }
95 ]
96 ],
97 mocks: [],
98 rootDir: 'containerised-jest'
99 },
100 resolver: {
101 _options: {
102 browser: false,
103 extensions: [
104 '.js',
105 '.json',
106 '.jsx',
107 '.ts',
108 '.tsx',
109 '.node'
110 ],
111 hasCoreModules: true,
112 moduleDirectories: [
113 'node_modules'
114 ],
115 moduleNameMapper: null,
116 resolver: null,
117 rootDir: 'containerised-jest'
118 },
119 _supportsNativePlatform: false,
120 _moduleMap: {
121 duplicates: [],
122 map: [
123 [
124 'containerised-jest',
125 {
126 g: [
127 'package.json',
128 1
129 ]
130 }
131 ]
132 ],
133 mocks: [],
134 rootDir: 'containerised-jest'
135 },
136 _moduleIDCache: {},
137 _moduleNameCache: {},
138 _modulePathCache: {}
139 }
140 },
141 path: 'containerised-jest/src/form/__tests__/selectField.test.js'
142 },
143 '1': {
144 console: [],
145 failureMessage: null,
146 numFailingTests: 0,
147 numPassingTests: 5,
148 numPendingTests: 0,
149 numTodoTests: 0,
150 perfStats: {
151 end: 1552149693620,
152 start: 1552149691845
153 },
154 snapshot: {
155 added: 0,
156 fileDeleted: false,
157 matched: 5,
158 unchecked: 0,
159 unmatched: 0,
160 updated: 0,
161 uncheckedKeys: []
162 },
163 testFilePath: 'containerised-jest/src/form/__tests__/selectField.test.js',
164 testResults: [
165 {
166 ancestorTitles: [
167 'SelectField Component'
168 ],
169 duration: 16,
170 failureMessages: [],
171 fullName: 'SelectField Component renders text input correctly',
172 location: null,
173 numPassingAsserts: 0,
174 status: 'passed',
175 title: 'renders text input correctly'
176 },
177 {
178 ancestorTitles: [
179 'SelectField Component'
180 ],
181 duration: 2,
182 failureMessages: [],
183 fullName: 'SelectField Component renders required input correctly',
184 location: null,
185 numPassingAsserts: 0,
186 status: 'passed',
187 title: 'renders required input correctly'
188 },
189 {
190 ancestorTitles: [
191 'SelectField Component'
192 ],
193 duration: 1,
194 failureMessages: [],
195 fullName: 'SelectField Component renders input with errors correctly',
196 location: null,
197 numPassingAsserts: 0,
198 status: 'passed',
199 title: 'renders input with errors correctly'
200 },
201 {
202 ancestorTitles: [
203 'SelectField Component'
204 ],
205 duration: 2,
206 failureMessages: [],
207 fullName: 'SelectField Component renders input with warnings correctly',
208 location: null,
209 numPassingAsserts: 0,
210 status: 'passed',
211 title: 'renders input with warnings correctly'
212 },
213 {
214 ancestorTitles: [
215 'SelectField Component'
216 ],
217 duration: 1,
218 failureMessages: [],
219 fullName: 'SelectField Component renders input with no errors or warnings but was touched correctly',
220 location: null,
221 numPassingAsserts: 0,
222 status: 'passed',
223 title: 'renders input with no errors or warnings but was touched correctly'
224 }
225 ],
226 sourceMaps: {},
227 skipped: false,
228 leaks: false
229 },
230 '2': {
231 numFailedTestSuites: 0,
232 numFailedTests: 0,
233 numPassedTestSuites: 1,
234 numPassedTests: 5,
235 numPendingTestSuites: 0,
236 numPendingTests: 0,
237 numRuntimeErrorTestSuites: 0,
238 numTodoTests: 0,
239 numTotalTestSuites: 15,
240 numTotalTests: 5,
241 openHandles: [],
242 snapshot: {
243 added: 0,
244 didUpdate: false,
245 failure: false,
246 filesAdded: 0,
247 filesRemoved: 0,
248 filesUnmatched: 0,
249 filesUpdated: 0,
250 matched: 5,
251 total: 5,
252 unchecked: 0,
253 uncheckedKeysByFile: [],
254 unmatched: 0,
255 updated: 0
256 },
257 startTime: 1552149691390,
258 success: false,
259 testResults: [
260 {
261 console: [],
262 failureMessage: null,
263 numFailingTests: 0,
264 numPassingTests: 5,
265 numPendingTests: 0,
266 numTodoTests: 0,
267 perfStats: {
268 end: 1552149693620,
269 start: 1552149691845
270 },
271 snapshot: {
272 added: 0,
273 fileDeleted: false,
274 matched: 5,
275 unchecked: 0,
276 unmatched: 0,
277 updated: 0,
278 uncheckedKeys: []
279 },
280 testFilePath: 'containerised-jest/src/form/__tests__/selectField.test.js',
281 testResults: [
282 {
283 ancestorTitles: [
284 'SelectField Component'
285 ],
286 duration: 16,
287 failureMessages: [],
288 fullName: 'SelectField Component renders text input correctly',
289 location: null,
290 numPassingAsserts: 0,
291 status: 'passed',
292 title: 'renders text input correctly'
293 },
294 {
295 ancestorTitles: [
296 'SelectField Component'
297 ],
298 duration: 2,
299 failureMessages: [],
300 fullName: 'SelectField Component renders required input correctly',
301 location: null,
302 numPassingAsserts: 0,
303 status: 'passed',
304 title: 'renders required input correctly'
305 },
306 {
307 ancestorTitles: [
308 'SelectField Component'
309 ],
310 duration: 1,
311 failureMessages: [],
312 fullName: 'SelectField Component renders input with errors correctly',
313 location: null,
314 numPassingAsserts: 0,
315 status: 'passed',
316 title: 'renders input with errors correctly'
317 },
318 {
319 ancestorTitles: [
320 'SelectField Component'
321 ],
322 duration: 2,
323 failureMessages: [],
324 fullName: 'SelectField Component renders input with warnings correctly',
325 location: null,
326 numPassingAsserts: 0,
327 status: 'passed',
328 title: 'renders input with warnings correctly'
329 },
330 {
331 ancestorTitles: [
332 'SelectField Component'
333 ],
334 duration: 1,
335 failureMessages: [],
336 fullName: 'SelectField Component renders input with no errors or warnings but was touched correctly',
337 location: null,
338 numPassingAsserts: 0,
339 status: 'passed',
340 title: 'renders input with no errors or warnings but was touched correctly'
341 }
342 ],
343 sourceMaps: {},
344 skipped: false,
345 leaks: false
346 }
347 ],
348 wasInterrupted: false
349 }
350}
Arguments passed to onTestResult

onRunComplete

The first argument doesn’t seem to contain any information but I believe it would contain information about the last test run.

The second argument contains the results of the test run such as:

  • The total number of run test suites & test cases
  • The total number of passed/failed test suites
  • The total number of passed/failed tests
  • Snapshot information for the test run
  • Test result information for every test suite which contains test result information for every test in the test suite
1{
2 '0': {},
3 '1': {
4 numFailedTestSuites: 0,
5 numFailedTests: 0,
6 numPassedTestSuites: 15,
7 numPassedTests: 46,
8 numPendingTestSuites: 0,
9 numPendingTests: 0,
10 numRuntimeErrorTestSuites: 0,
11 numTodoTests: 0,
12 numTotalTestSuites: 15,
13 numTotalTests: 46,
14 openHandles: [],
15 snapshot: {
16 added: 0,
17 didUpdate: false,
18 failure: false,
19 filesAdded: 0,
20 filesRemoved: 0,
21 filesUnmatched: 0,
22 filesUpdated: 0,
23 matched: 46,
24 total: 46,
25 unchecked: 0,
26 uncheckedKeysByFile: [],
27 unmatched: 0,
28 updated: 0
29 },
30 startTime: 1552149691390,
31 success: false,
32 testResults: [
33 {
34 console: [],
35 failureMessage: null,
36 numFailingTests: 0,
37 numPassingTests: 5,
38 numPendingTests: 0,
39 numTodoTests: 0,
40 perfStats: {
41 end: 1552149693620,
42 start: 1552149691845
43 },
44 snapshot: {
45 added: 0,
46 fileDeleted: false,
47 matched: 5,
48 unchecked: 0,
49 unmatched: 0,
50 updated: 0,
51 uncheckedKeys: []
52 },
53 testFilePath: 'containerised-jest/src/form/__tests__/selectField.test.js',
54 testResults: [
55 {
56 ancestorTitles: [
57 'SelectField Component'
58 ],
59 duration: 16,
60 failureMessages: [],
61 fullName: 'SelectField Component renders text input correctly',
62 location: null,
63 numPassingAsserts: 0,
64 status: 'passed',
65 title: 'renders text input correctly'
66 },
67 {
68 ancestorTitles: [
69 'SelectField Component'
70 ],
71 duration: 2,
72 failureMessages: [],
73 fullName: 'SelectField Component renders required input correctly',
74 location: null,
75 numPassingAsserts: 0,
76 status: 'passed',
77 title: 'renders required input correctly'
78 },
79 {
80 ancestorTitles: [
81 'SelectField Component'
82 ],
83 duration: 1,
84 failureMessages: [],
85 fullName: 'SelectField Component renders input with errors correctly',
86 location: null,
87 numPassingAsserts: 0,
88 status: 'passed',
89 title: 'renders input with errors correctly'
90 },
91 {
92 ancestorTitles: [
93 'SelectField Component'
94 ],
95 duration: 2,
96 failureMessages: [],
97 fullName: 'SelectField Component renders input with warnings correctly',
98 location: null,
99 numPassingAsserts: 0,
100 status: 'passed',
101 title: 'renders input with warnings correctly'
102 },
103 {
104 ancestorTitles: [
105 'SelectField Component'
106 ],
107 duration: 1,
108 failureMessages: [],
109 fullName: 'SelectField Component renders input with no errors or warnings but was touched correctly',
110 location: null,
111 numPassingAsserts: 0,
112 status: 'passed',
113 title: 'renders input with no errors or warnings but was touched correctly'
114 }
115 ],
116 sourceMaps: {},
117 skipped: false,
118 leaks: false
119 },
120 {
121 console: [],
122 failureMessage: null,
123 numFailingTests: 0,
124 numPassingTests: 6,
125 numPendingTests: 0,
126 numTodoTests: 0,
127 perfStats: {
128 end: 1552149693647,
129 start: 1552149691846
130 },
131 snapshot: {
132 added: 0,
133 fileDeleted: false,
134 matched: 6,
135 unchecked: 0,
136 unmatched: 0,
137 updated: 0,
138 uncheckedKeys: []
139 },
140 testFilePath: 'containerised-jest/src/form/__tests__/inputField.test.js',
141 testResults: [
142 {
143 ancestorTitles: [
144 'InputField Component'
145 ],
146 duration: 18,
147 failureMessages: [],
148 fullName: 'InputField Component renders text input correctly',
149 location: null,
150 numPassingAsserts: 0,
151 status: 'passed',
152 title: 'renders text input correctly'
153 },
154 {
155 ancestorTitles: [
156 'InputField Component'
157 ],
158 duration: 5,
159 failureMessages: [],
160 fullName: 'InputField Component renders password input correctly',
161 location: null,
162 numPassingAsserts: 0,
163 status: 'passed',
164 title: 'renders password input correctly'
165 },
166 {
167 ancestorTitles: [
168 'InputField Component'
169 ],
170 duration: 1,
171 failureMessages: [],
172 fullName: 'InputField Component renders required input correctly',
173 location: null,
174 numPassingAsserts: 0,
175 status: 'passed',
176 title: 'renders required input correctly'
177 },
178 {
179 ancestorTitles: [
180 'InputField Component'
181 ],
182 duration: 2,
183 failureMessages: [],
184 fullName: 'InputField Component renders input with errors correctly',
185 location: null,
186 numPassingAsserts: 0,
187 status: 'passed',
188 title: 'renders input with errors correctly'
189 },
190 {
191 ancestorTitles: [
192 'InputField Component'
193 ],
194 duration: 0,
195 failureMessages: [],
196 fullName: 'InputField Component renders input with warnings correctly',
197 location: null,
198 numPassingAsserts: 0,
199 status: 'passed',
200 title: 'renders input with warnings correctly'
201 },
202 {
203 ancestorTitles: [
204 'InputField Component'
205 ],
206 duration: 1,
207 failureMessages: [],
208 fullName: 'InputField Component renders input with no errors or warnings but was touched correctly',
209 location: null,
210 numPassingAsserts: 0,
211 status: 'passed',
212 title: 'renders input with no errors or warnings but was touched correctly'
213 }
214 ],
215 sourceMaps: {},
216 skipped: false,
217 leaks: false
218 },
219 {
220 console: [],
221 failureMessage: null,
222 numFailingTests: 0,
223 numPassingTests: 5,
224 numPendingTests: 0,
225 numTodoTests: 0,
226 perfStats: {
227 end: 1552149693664,
228 start: 1552149691852
229 },
230 snapshot: {
231 added: 0,
232 fileDeleted: false,
233 matched: 5,
234 unchecked: 0,
235 unmatched: 0,
236 updated: 0,
237 uncheckedKeys: []
238 },
239 testFilePath: 'containerised-jest/src/form/__tests__/fieldField.test.js',
240 testResults: [
241 {
242 ancestorTitles: [
243 'FileField Component'
244 ],
245 duration: 20,
246 failureMessages: [],
247 fullName: 'FileField Component renders file input correctly',
248 location: null,
249 numPassingAsserts: 0,
250 status: 'passed',
251 title: 'renders file input correctly'
252 },
253 {
254 ancestorTitles: [
255 'FileField Component'
256 ],
257 duration: 2,
258 failureMessages: [],
259 fullName: 'FileField Component renders required input correctly',
260 location: null,
261 numPassingAsserts: 0,
262 status: 'passed',
263 title: 'renders required input correctly'
264 },
265 {
266 ancestorTitles: [
267 'FileField Component'
268 ],
269 duration: 2,
270 failureMessages: [],
271 fullName: 'FileField Component renders input with errors correctly',
272 location: null,
273 numPassingAsserts: 0,
274 status: 'passed',
275 title: 'renders input with errors correctly'
276 },
277 {
278 ancestorTitles: [
279 'FileField Component'
280 ],
281 duration: 1,
282 failureMessages: [],
283 fullName: 'FileField Component renders input with warnings correctly',
284 location: null,
285 numPassingAsserts: 0,
286 status: 'passed',
287 title: 'renders input with warnings correctly'
288 },
289 {
290 ancestorTitles: [
291 'FileField Component'
292 ],
293 duration: 1,
294 failureMessages: [],
295 fullName: 'FileField Component renders input with no errors or warnings but was touched correctly',
296 location: null,
297 numPassingAsserts: 0,
298 status: 'passed',
299 title: 'renders input with no errors or warnings but was touched correctly'
300 }
301 ],
302 sourceMaps: {},
303 skipped: false,
304 leaks: false
305 },
306 {
307 console: [],
308 failureMessage: null,
309 numFailingTests: 0,
310 numPassingTests: 5,
311 numPendingTests: 0,
312 numTodoTests: 0,
313 perfStats: {
314 end: 1552149693944,
315 start: 1552149693654
316 },
317 snapshot: {
318 added: 0,
319 fileDeleted: false,
320 matched: 5,
321 unchecked: 0,
322 unmatched: 0,
323 updated: 0,
324 uncheckedKeys: []
325 },
326 testFilePath: 'containerised-jest/src/form/__tests__/buttonField.test.js',
327 testResults: [
328 {
329 ancestorTitles: [
330 'ButtonField Component'
331 ],
332 duration: 4,
333 failureMessages: [],
334 fullName: 'ButtonField Component renders submit button correctly',
335 location: null,
336 numPassingAsserts: 0,
337 status: 'passed',
338 title: 'renders submit button correctly'
339 },
340 {
341 ancestorTitles: [
342 'ButtonField Component'
343 ],
344 duration: 2,
345 failureMessages: [],
346 fullName: 'ButtonField Component renders delete button correctly',
347 location: null,
348 numPassingAsserts: 0,
349 status: 'passed',
350 title: 'renders delete button correctly'
351 },
352 {
353 ancestorTitles: [
354 'ButtonField Component'
355 ],
356 duration: 1,
357 failureMessages: [],
358 fullName: 'ButtonField Component renders clear form button correctly',
359 location: null,
360 numPassingAsserts: 0,
361 status: 'passed',
362 title: 'renders clear form button correctly'
363 },
364 {
365 ancestorTitles: [
366 'ButtonField Component'
367 ],
368 duration: 1,
369 failureMessages: [],
370 fullName: 'ButtonField Component renders other button type correctly',
371 location: null,
372 numPassingAsserts: 0,
373 status: 'passed',
374 title: 'renders other button type correctly'
375 },
376 {
377 ancestorTitles: [
378 'ButtonField Component'
379 ],
380 duration: 2,
381 failureMessages: [],
382 fullName: 'ButtonField Component renders button with handleClick function correctly',
383 location: null,
384 numPassingAsserts: 0,
385 status: 'passed',
386 title: 'renders button with handleClick function correctly'
387 }
388 ],
389 sourceMaps: {},
390 skipped: false,
391 leaks: false
392 },
393 {
394 console: [],
395 failureMessage: null,
396 numFailingTests: 0,
397 numPassingTests: 3,
398 numPendingTests: 0,
399 numTodoTests: 0,
400 perfStats: {
401 end: 1552149694050,
402 start: 1552149693674
403 },
404 snapshot: {
405 added: 0,
406 fileDeleted: false,
407 matched: 3,
408 unchecked: 0,
409 unmatched: 0,
410 updated: 0,
411 uncheckedKeys: []
412 },
413 testFilePath: 'containerised-jest/src/form/__tests__/buttonGroupField.test.js',
414 testResults: [
415 {
416 ancestorTitles: [
417 'ButtonGroupField Component'
418 ],
419 duration: 5,
420 failureMessages: [],
421 fullName: 'ButtonGroupField Component renders left button group correctly',
422 location: null,
423 numPassingAsserts: 0,
424 status: 'passed',
425 title: 'renders left button group correctly'
426 },
427 {
428 ancestorTitles: [
429 'ButtonGroupField Component'
430 ],
431 duration: 1,
432 failureMessages: [],
433 fullName: 'ButtonGroupField Component renders centered button group correctly',
434 location: null,
435 numPassingAsserts: 0,
436 status: 'passed',
437 title: 'renders centered button group correctly'
438 },
439 {
440 ancestorTitles: [
441 'ButtonGroupField Component'
442 ],
443 duration: 1,
444 failureMessages: [],
445 fullName: 'ButtonGroupField Component renders right button group correctly',
446 location: null,
447 numPassingAsserts: 0,
448 status: 'passed',
449 title: 'renders right button group correctly'
450 }
451 ],
452 sourceMaps: {},
453 skipped: false,
454 leaks: false
455 },
456 {
457 console: [],
458 failureMessage: null,
459 numFailingTests: 0,
460 numPassingTests: 5,
461 numPendingTests: 0,
462 numTodoTests: 0,
463 perfStats: {
464 end: 1552149694101,
465 start: 1552149693629
466 },
467 snapshot: {
468 added: 0,
469 fileDeleted: false,
470 matched: 5,
471 unchecked: 0,
472 unmatched: 0,
473 updated: 0,
474 uncheckedKeys: []
475 },
476 testFilePath: 'containerised-jest/src/__tests__/header.test.js',
477 testResults: [
478 {
479 ancestorTitles: [
480 'Header Component'
481 ],
482 duration: 8,
483 failureMessages: [],
484 fullName: 'Header Component renders correctly when authenticated',
485 location: null,
486 numPassingAsserts: 0,
487 status: 'passed',
488 title: 'renders correctly when authenticated'
489 },
490 {
491 ancestorTitles: [
492 'Header Component'
493 ],
494 duration: 3,
495 failureMessages: [],
496 fullName: 'Header Component renders correctly when not authenticated',
497 location: null,
498 numPassingAsserts: 0,
499 status: 'passed',
500 title: 'renders correctly when not authenticated'
501 },
502 {
503 ancestorTitles: [
504 'Header Component'
505 ],
506 duration: 1,
507 failureMessages: [],
508 fullName: 'Header Component renders correctly with logout function defined',
509 location: null,
510 numPassingAsserts: 0,
511 status: 'passed',
512 title: 'renders correctly with logout function defined'
513 },
514 {
515 ancestorTitles: [
516 'Header Component'
517 ],
518 duration: 1,
519 failureMessages: [],
520 fullName: 'Header Component renders correctly with login function defined',
521 location: null,
522 numPassingAsserts: 0,
523 status: 'passed',
524 title: 'renders correctly with login function defined'
525 },
526 {
527 ancestorTitles: [
528 'Header Component'
529 ],
530 duration: 2,
531 failureMessages: [],
532 fullName: 'Header Component renders correctly with both functions defined',
533 location: null,
534 numPassingAsserts: 0,
535 status: 'passed',
536 title: 'renders correctly with both functions defined'
537 }
538 ],
539 sourceMaps: {},
540 skipped: false,
541 leaks: false
542 },
543 {
544 console: [],
545 failureMessage: null,
546 numFailingTests: 0,
547 numPassingTests: 2,
548 numPendingTests: 0,
549 numTodoTests: 0,
550 perfStats: {
551 end: 1552149694434,
552 start: 1552149694059
553 },
554 snapshot: {
555 added: 0,
556 fileDeleted: false,
557 matched: 2,
558 unchecked: 0,
559 unmatched: 0,
560 updated: 0,
561 uncheckedKeys: []
562 },
563 testFilePath: 'containerised-jest/src/profile/__tests__/gridImage.test.js',
564 testResults: [
565 {
566 ancestorTitles: [
567 'Grid Image Component'
568 ],
569 duration: 8,
570 failureMessages: [],
571 fullName: 'Grid Image Component renders correctly with number',
572 location: null,
573 numPassingAsserts: 0,
574 status: 'passed',
575 title: 'renders correctly with number'
576 },
577 {
578 ancestorTitles: [
579 'Grid Image Component'
580 ],
581 duration: 2,
582 failureMessages: [],
583 fullName: 'Grid Image Component renders correctly without number',
584 location: null,
585 numPassingAsserts: 0,
586 status: 'passed',
587 title: 'renders correctly without number'
588 }
589 ],
590 sourceMaps: {},
591 skipped: false,
592 leaks: false
593 },
594 {
595 console: [],
596 failureMessage: null,
597 numFailingTests: 0,
598 numPassingTests: 2,
599 numPendingTests: 0,
600 numTodoTests: 0,
601 perfStats: {
602 end: 1552149694508,
603 start: 1552149693950
604 },
605 snapshot: {
606 added: 0,
607 fileDeleted: false,
608 matched: 2,
609 unchecked: 0,
610 unmatched: 0,
611 updated: 0,
612 uncheckedKeys: []
613 },
614 testFilePath: 'containerised-jest/src/__tests__/footer.test.js',
615 testResults: [
616 {
617 ancestorTitles: [
618 'Footer Component'
619 ],
620 duration: 8,
621 failureMessages: [],
622 fullName: 'Footer Component renders correctly when authenticated',
623 location: null,
624 numPassingAsserts: 0,
625 status: 'passed',
626 title: 'renders correctly when authenticated'
627 },
628 {
629 ancestorTitles: [
630 'Footer Component'
631 ],
632 duration: 1,
633 failureMessages: [],
634 fullName: 'Footer Component renders correctly when not authenticated',
635 location: null,
636 numPassingAsserts: 0,
637 status: 'passed',
638 title: 'renders correctly when not authenticated'
639 }
640 ],
641 sourceMaps: {},
642 skipped: false,
643 leaks: false
644 },
645 {
646 console: [],
647 failureMessage: null,
648 numFailingTests: 0,
649 numPassingTests: 2,
650 numPendingTests: 0,
651 numTodoTests: 0,
652 perfStats: {
653 end: 1552149694531,
654 start: 1552149694107
655 },
656 snapshot: {
657 added: 0,
658 fileDeleted: false,
659 matched: 2,
660 unchecked: 0,
661 unmatched: 0,
662 updated: 0,
663 uncheckedKeys: []
664 },
665 testFilePath: 'containerised-jest/src/profile/__tests__/pokedexEntries.test.js',
666 testResults: [
667 {
668 ancestorTitles: [
669 'Pokedex Entries Component'
670 ],
671 duration: 7,
672 failureMessages: [],
673 fullName: 'Pokedex Entries Component renders with entries correctly',
674 location: null,
675 numPassingAsserts: 0,
676 status: 'passed',
677 title: 'renders with entries correctly'
678 },
679 {
680 ancestorTitles: [
681 'Pokedex Entries Component'
682 ],
683 duration: 1,
684 failureMessages: [],
685 fullName: 'Pokedex Entries Component renders without entries correctly',
686 location: null,
687 numPassingAsserts: 0,
688 status: 'passed',
689 title: 'renders without entries correctly'
690 }
691 ],
692 sourceMaps: {},
693 skipped: false,
694 leaks: false
695 },
696 {
697 console: [],
698 failureMessage: null,
699 numFailingTests: 0,
700 numPassingTests: 2,
701 numPendingTests: 0,
702 numTodoTests: 0,
703 perfStats: {
704 end: 1552149694747,
705 start: 1552149694439
706 },
707 snapshot: {
708 added: 0,
709 fileDeleted: false,
710 matched: 2,
711 unchecked: 0,
712 unmatched: 0,
713 updated: 0,
714 uncheckedKeys: []
715 },
716 testFilePath: 'containerised-jest/src/profile/__tests__/featuredImage.test.js',
717 testResults: [
718 {
719 ancestorTitles: [
720 'Featured Image Component'
721 ],
722 duration: 5,
723 failureMessages: [],
724 fullName: 'Featured Image Component renders unauthenticated correctly',
725 location: null,
726 numPassingAsserts: 0,
727 status: 'passed',
728 title: 'renders unauthenticated correctly'
729 },
730 {
731 ancestorTitles: [
732 'Featured Image Component'
733 ],
734 duration: 2,
735 failureMessages: [],
736 fullName: 'Featured Image Component renders authenticated correctly',
737 location: null,
738 numPassingAsserts: 0,
739 status: 'passed',
740 title: 'renders authenticated correctly'
741 }
742 ],
743 sourceMaps: {},
744 skipped: false,
745 leaks: false
746 },
747 {
748 console: [],
749 failureMessage: null,
750 numFailingTests: 0,
751 numPassingTests: 2,
752 numPendingTests: 0,
753 numTodoTests: 0,
754 perfStats: {
755 end: 1552149694835,
756 start: 1552149694514
757 },
758 snapshot: {
759 added: 0,
760 fileDeleted: false,
761 matched: 2,
762 unchecked: 0,
763 unmatched: 0,
764 updated: 0,
765 uncheckedKeys: []
766 },
767 testFilePath: 'containerised-jest/src/__tests__/modal.test.js',
768 testResults: [
769 {
770 ancestorTitles: [
771 'Modal Component'
772 ],
773 duration: 4,
774 failureMessages: [],
775 fullName: 'Modal Component renders correctly when modal is active',
776 location: null,
777 numPassingAsserts: 0,
778 status: 'passed',
779 title: 'renders correctly when modal is active'
780 },
781 {
782 ancestorTitles: [
783 'Modal Component'
784 ],
785 duration: 2,
786 failureMessages: [],
787 fullName: 'Modal Component renders correctly when modal is inactive',
788 location: null,
789 numPassingAsserts: 0,
790 status: 'passed',
791 title: 'renders correctly when modal is inactive'
792 }
793 ],
794 sourceMaps: {},
795 skipped: false,
796 leaks: false
797 },
798 {
799 console: [],
800 failureMessage: null,
801 numFailingTests: 0,
802 numPassingTests: 2,
803 numPendingTests: 0,
804 numTodoTests: 0,
805 perfStats: {
806 end: 1552149694956,
807 start: 1552149694537
808 },
809 snapshot: {
810 added: 0,
811 fileDeleted: false,
812 matched: 2,
813 unchecked: 0,
814 unmatched: 0,
815 updated: 0,
816 uncheckedKeys: []
817 },
818 testFilePath: 'containerised-jest/src/profile/__tests__/tabBar.test.js',
819 testResults: [
820 {
821 ancestorTitles: [
822 'Tab Bar Component'
823 ],
824 duration: 60,
825 failureMessages: [],
826 fullName: 'Tab Bar Component renders with tabs and panels correctly',
827 location: null,
828 numPassingAsserts: 0,
829 status: 'passed',
830 title: 'renders with tabs and panels correctly'
831 },
832 {
833 ancestorTitles: [
834 'Tab Bar Component'
835 ],
836 duration: 2,
837 failureMessages: [],
838 fullName: 'Tab Bar Component renders without tabs and panels correctly',
839 location: null,
840 numPassingAsserts: 0,
841 status: 'passed',
842 title: 'renders without tabs and panels correctly'
843 }
844 ],
845 sourceMaps: {},
846 skipped: false,
847 leaks: false
848 },
849 {
850 console: [],
851 failureMessage: null,
852 numFailingTests: 0,
853 numPassingTests: 2,
854 numPendingTests: 0,
855 numTodoTests: 0,
856 perfStats: {
857 end: 1552149695061,
858 start: 1552149694766
859 },
860 snapshot: {
861 added: 0,
862 fileDeleted: false,
863 matched: 2,
864 unchecked: 0,
865 unmatched: 0,
866 updated: 0,
867 uncheckedKeys: []
868 },
869 testFilePath: 'containerised-jest/src/profile/__tests__/imageGrid.test.js',
870 testResults: [
871 {
872 ancestorTitles: [
873 'Image Grid Component'
874 ],
875 duration: 6,
876 failureMessages: [],
877 fullName: 'Image Grid Component renders with images correctly',
878 location: null,
879 numPassingAsserts: 0,
880 status: 'passed',
881 title: 'renders with images correctly'
882 },
883 {
884 ancestorTitles: [
885 'Image Grid Component'
886 ],
887 duration: 2,
888 failureMessages: [],
889 fullName: 'Image Grid Component renders without images correctly',
890 location: null,
891 numPassingAsserts: 0,
892 status: 'passed',
893 title: 'renders without images correctly'
894 }
895 ],
896 sourceMaps: {},
897 skipped: false,
898 leaks: false
899 },
900 {
901 console: [],
902 failureMessage: null,
903 numFailingTests: 0,
904 numPassingTests: 2,
905 numPendingTests: 0,
906 numTodoTests: 0,
907 perfStats: {
908 end: 1552149695064,
909 start: 1552149694859
910 },
911 snapshot: {
912 added: 0,
913 fileDeleted: false,
914 matched: 2,
915 unchecked: 0,
916 unmatched: 0,
917 updated: 0,
918 uncheckedKeys: []
919 },
920 testFilePath: 'containerised-jest/src/__tests__/button.test.js',
921 testResults: [
922 {
923 ancestorTitles: [
924 'Button Component'
925 ],
926 duration: 3,
927 failureMessages: [],
928 fullName: 'Button Component renders correctly without onClick being set',
929 location: null,
930 numPassingAsserts: 0,
931 status: 'passed',
932 title: 'renders correctly without onClick being set'
933 },
934 {
935 ancestorTitles: [
936 'Button Component'
937 ],
938 duration: 1,
939 failureMessages: [],
940 fullName: 'Button Component renders correctly with onClick set',
941 location: null,
942 numPassingAsserts: 0,
943 status: 'passed',
944 title: 'renders correctly with onClick set'
945 }
946 ],
947 sourceMaps: {},
948 skipped: false,
949 leaks: false
950 },
951 {
952 console: [],
953 failureMessage: null,
954 numFailingTests: 0,
955 numPassingTests: 1,
956 numPendingTests: 0,
957 numTodoTests: 0,
958 perfStats: {
959 end: 1552149695128,
960 start: 1552149694974
961 },
962 snapshot: {
963 added: 0,
964 fileDeleted: false,
965 matched: 1,
966 unchecked: 0,
967 unmatched: 0,
968 updated: 0,
969 uncheckedKeys: []
970 },
971 testFilePath: 'containerised-jest/src/__tests__/logo.test.js',
972 testResults: [
973 {
974 ancestorTitles: [
975 'Logo Component'
976 ],
977 duration: 2,
978 failureMessages: [],
979 fullName: 'Logo Component renders correctly',
980 location: null,
981 numPassingAsserts: 0,
982 status: 'passed',
983 title: 'renders correctly'
984 }
985 ],
986 sourceMaps: {},
987 skipped: false,
988 leaks: false
989 }
990 ],
991 wasInterrupted: false
992 }
993}
Arguments passed to onRunComplete

More resources

I found the following thread over on the Jest repo helpful when figuring out what hooks the test reporter could use: https://github.com/facebook/jest/issues/4471

You can find many different jest reporters on npm or Github.

I’ve also created an example project with jsdoc for a custom reporter which logs the arguments passed to each hook to help document the data custom reporters can work with.