diff --git a/.eslintrc.yml b/.eslintrc.yml
index 549f17715931a773cc2f74736a7c6fe9a2ba4ac2..f5814639b361554ec849170e21f81710a7a39906 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -105,6 +105,8 @@ overrides:
           message: 'Avoid using "setData" on VTU wrapper'
         - selector: MemberExpression[object.type!='ThisExpression'][property.type='Identifier'][property.name='$nextTick']
           message: 'Using $nextTick from a component instance is discouraged. Import nextTick directly from the Vue package.'
+        - selector: Identifier[name='setImmediate']
+          message: 'Prefer explicit waitForPromises (or equivalent), or jest.runAllTimers (or equivalent) to vague setImmediate calls.'
   - files:
       - 'config/**/*'
       - 'scripts/**/*'
diff --git a/spec/frontend/__helpers__/flush_promises.js b/spec/frontend/__helpers__/flush_promises.js
index 5287a060753c431bf3b7458b5ec9411234fc0001..eefc2ed7c17f2804da79ab488b23142e8c09e5b2 100644
--- a/spec/frontend/__helpers__/flush_promises.js
+++ b/spec/frontend/__helpers__/flush_promises.js
@@ -1,3 +1,4 @@
 export default function flushPromises() {
+  // eslint-disable-next-line no-restricted-syntax
   return new Promise(setImmediate);
 }
diff --git a/spec/frontend/__helpers__/mocks/axios_utils.js b/spec/frontend/__helpers__/mocks/axios_utils.js
index 674563b9f2818fb0b61f4afdb02f107fecd68cd6..b1efd29dc8d5d3ffdfcc8fb11309b2543b6122c4 100644
--- a/spec/frontend/__helpers__/mocks/axios_utils.js
+++ b/spec/frontend/__helpers__/mocks/axios_utils.js
@@ -25,6 +25,7 @@ const onRequest = () => {
 // Use setImmediate to alloow the response interceptor to finish
 const onResponse = (config) => {
   activeRequests -= 1;
+  // eslint-disable-next-line no-restricted-syntax
   setImmediate(() => {
     events.emit('response', config);
   });
@@ -43,6 +44,7 @@ const subscribeToResponse = (predicate = () => true) =>
 
     // If a request has been made synchronously, setImmediate waits for it to be
     // processed and the counter incremented.
+    // eslint-disable-next-line no-restricted-syntax
     setImmediate(listener);
   });
 
diff --git a/spec/frontend/__helpers__/vuex_action_helper.js b/spec/frontend/__helpers__/vuex_action_helper.js
index e482a8fbc719da8a95f13c9811eac45520ed3e96..68203b544ef9278be6167a41991da93de2cf0915 100644
--- a/spec/frontend/__helpers__/vuex_action_helper.js
+++ b/spec/frontend/__helpers__/vuex_action_helper.js
@@ -116,6 +116,7 @@ export default (
     payload,
   );
 
+  // eslint-disable-next-line no-restricted-syntax
   return (result || new Promise((resolve) => setImmediate(resolve)))
     .catch((error) => {
       validateResults();
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
index d7acf75fc95686599c780ca11a2fdd071f6039ab..8465b57c6603fe366975aa6efbe268fab61f0179 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -123,6 +123,7 @@ class CustomEnvironment extends JSDOMEnvironment {
     // Reset `Date` so that Jest can report timing accurately *roll eyes*...
     setGlobalDateToRealDate();
 
+    // eslint-disable-next-line no-restricted-syntax
     await new Promise(setImmediate);
 
     if (this.rejectedPromises.length > 0) {
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index 4fe51db8412b6561868799488269d7d328f31ea2..6c336152e9a446a694ad4abd96c28b1e8bc26cd1 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -8,6 +8,7 @@ initializeTestTimeout(process.env.CI ? 6000 : 500);
 
 afterEach(() =>
   // give Promises a bit more time so they fail the right test
+  // eslint-disable-next-line no-restricted-syntax
   new Promise(setImmediate).then(() => {
     // wait for pending setTimeout()s
     jest.runOnlyPendingTimers();