Skip to main content

Avoid Executing Code After Calling done() in Asynchronous Tests

High
TestingReliabilityasynchronous

What is it?

This practice highlights tests that execute statements after calling Mocha's done() callback. The callback is used to signal the end of an asynchronous test, and any code following it may execute out of order, leading to unpredictable test behavior.

Why apply it?

Calling done() indicates that the test is complete. Executing code after this call can result in operations running unpredictably—possibly affecting other tests—thus compromising the integrity and maintainability of your test suite.

How to Fix it?

Make sure that any necessary operations or logging are done before calling done(). The done() callback must be the final statement to ensure the test lifecycle is properly respected.

Examples

Example 1:

Negative

Incorrect implementation that violates the practice.

import { expect } from "chai";

describe('Asynchronous Operation', function() {
it('should complete async operation correctly', function(done) {
setTimeout(function() {
// Perform assertions
expect(Array.isArray([])).to.be.true;
expect("mocha".length).to.equal(5);

// Calling done() immediately after assertions
done();

// Code executed after done() - Noncompliant: may lead to unexpected order of execution
console.log('This log may execute out of the intended test lifecycle.');
}, 1000);
});
});

Example 2:

Positive

Correct implementation following the practice.

import { expect } from "chai";

describe('Promise-based Async Test', function() {
it('handles asynchronous operations correctly', function(done) {
setTimeout(() => {
try {
const value = 42;
expect(value).to.equal(42);
// Log intermediate operations before finishing
console.log('Intermediate operations complete.');
} catch (error) {
// Signal error via done() if assertions fail
return done(error);
}
// Signal test completion after all operations
done();
}, 800);
});
});

Negative

Incorrect implementation that violates the practice.

import { expect } from "chai";

describe('Cleanup Process', function() {
it('performs cleanup before finishing the test', function(done) {
setTimeout(function() {
// Execute calculations first
const result = 3 + 7;
expect(result).to.equal(10);

// Signal test completion too early
done();

// Subsequent code after done() - Noncompliant: could lead to unpredictable behavior
console.log('Cleanup operations logged out of order.');
}, 500);
});
});

Example 3:

Positive

Correct implementation following the practice.

import { expect } from "chai";

describe('Cleanup Process', function() {
it('performs cleanup before finishing the test', function(done) {
setTimeout(function() {
// Execute calculations and cleanup operations
const result = 5 * 2;
expect(result).to.equal(10);

// Logging is done before signaling the end of the test
console.log('Cleanup operations completed successfully.');

// Signal that the asynchronous test is finished
done();
}, 500);
});
});

Negative

Incorrect implementation that violates the practice.

import { expect } from "chai";

describe('Promise-based Async Test', function() {
it('handles asynchronous operations correctly', function(done) {
setTimeout(() => {
try {
const value = 100;
expect(value).to.equal(100);
} catch (error) {
done(error);
}
// Calling done() too early
done();

// Code after done(), such as logging, is Noncompliant
console.log('This log should not execute after done() is called.');
}, 800);
});
});

Example 4:

Positive

Correct implementation following the practice.

import { expect } from "chai";

describe('Asynchronous Operation', function() {
it('should complete async operation correctly', function(done) {
setTimeout(function() {
// Perform assertions first
expect(1 + 1).to.equal(2);
expect("typescript".includes("script")).to.be.true;

// Log operations prior to test completion
console.log('Test operations completed successfully.');

// Signal test completion
done();
}, 1000);
});
});