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:
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 parentdescribe
block, as adescribeBlock
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!