Ken Muse

Mastering the Jest TestEnvironment Event Types


Last week, I wrote about how to create and use a custom TestEnvironment in Jest using TypeScript. I very briefly touched on the special method, handleTestEvent, that you can use to listen for events that occur during the test run. In this post, I want to dive deeper into the events raised by TestEnvironment to help you understand how to use them better.

When Jest is running, it will raise events to the TestEnvironment that indicate what step is being executed. For many of the events, it will include an object that contains additional details that you can use for processing the event. If you want to explore these in even more depth, you can log the event details from within the TestEnvironment to see what’s happening:

1 async handleTestEvent(event: Event, _state: State) {
2    console.log(event);
3 }

TL;DR

Just want a diagram to explain everything? No problem! Here’s my visual guide to the TestEnvironment methods and the events that are raised during the test run:

Jest TestEnvironment Event Lifecycle

The setup events

After the setup() method runs, the setup event is raised to notify interested listeners. This is followed by include_test_location_in_result, which is raised if the project configuration has configured testLocationInResults. Neither of these events provide additional details.

The next set of events happen once for each describe block in the test suite. The describe blocks are processed in the order in which they appear.

Jest raises the start_describe_definition for the describe block. The blockName property contains the name of the describe block. For example, it will include My test group for describe('My test group', () => { ... }). In doing this, it also executes the function that is passed to the describe method, running any code that is inside of it. This calls the before* and after* methods that register callbacks for those hooks.

The add_hook event is raised for the beforeEach, beforeAll, afterEach, and afterAll hooks. Each event will include the hookType property to indicate which hook is being added. The add_test event is also raised (with the testName property) for each test that is added to the describe block.

When the process completes, finish_describe_definition is raised, with the blockName property indicating the name of the describe block that was just processed.

The run events

Jest then sets a run_start event to indicate that the test process is about to begin. It has no properties. This is followed by run_describe_start. The run_describe_* events all include a describeBlock property that contains the details of the describe block that is being processed. This includes the blockName property.

The first run_describe_start event is raised for a special describe block with the name ROOT_DESCRIBE_BLOCK. After that, Jest starts raising events for each describe block and test. Again, this executes in the order in which the describe blocks and tests appear in the test suite.

The run_describe_start is raised to indicate a describe block is now being executed. Next, the hooks for that block are executed. Jest raises hook_start with the .hook.type property indicating which hook is being executed. This is typically beforeAll. After each hook, a hook_success or hook_failure is raised which will include the .hook.type and .describeBlock properties. For the next set of hooks, the .test.name property is also included to indicate which test is being processed.

The test_start event is raised to indicate that a test is being started. The test_* events include a .test object that includes details about the currently executing test, such as:

  • .name
  • .startedAt (as Unix Epoch time)
  • .errors (array of any errors that have occurred)
  • .numPassingAsserts (number of assertions that have passed)
  • .failing (is the test currently failing)
  • .parent (the parent describe block, as a describeBlock object)

This is followed by test_started. Next hook_start is raised for the beforeEach process. Similar to earlier, it is first raised with a .parent describe block with the name ROOT_DESCRIBE_BLOCK, followed by a hook_success event. Next, an event with the parent matching the current describe context, then the beforeEach callback is executed. This is also followed by hook_success or hook_failure events. Oddly enough, only the hook_success or hook_failure events include the .test property with the test details.

The test_fn_start event is raised to indicate that the test itself is being executed. After that event, the test function is run, followed by the test_fn_success or test_fn_failure event being raised. Jest then raises hook_start for afterEach and executes that callback, followed by another hook_success or hook_failure event.

At this point, the test is complete and test_done is raised. This event indicates that the test results are final, with .test.status indicating the final results. If there are any errors, they will be included in the .test.errors property, which will now have the complete details.

This process repeats for each test in the describe block. After that Jest raises hook_start for afterAll and runs those hooks, followed by hook_success or hook_failure. It then announces the completion of the describe processing with run_describe_finish. The process then repeats from the beginning for each describe block.

As a final note, there are other events that can be raised. Events such as test_skip, test_retry, and test_todo communicate the status of tests in the suite that required different handling. Your test environment is provided these details as well, allowing it to respond as needed.

Completion and cleanup

A final run_describe_finish is raised for the ROOT_DESCRIBE_BLOCK to indicate that all blocks have now completed. This is followed by run_finish (which has no properties, aside from a name) to indicate that the entire test run is complete.

A final event, teardown, is raised to indicate that the teardown method is about to be called. After this event completes, no more console logging is allowed. That means that you do have an event you can use if you need to do some cleanup with logging still enabled. Once this event has finished, the teardown method is of the TestEnvironment is invoked and the test suite is completed.

Conclusion

I hope this deeper dive makes it easier for implementors to understand the logic and processing that is occurring under the covers with Jest. It offers a lot of power for implementing cross-cutting concerns for tests, as well as a way to customize the processes. It’s a lot to take in, but that is often the case with complex frameworks.

Happy DevOp’ing!