Encountered the error message "Async callback did not complete within the designated 5000 ms timeframe" when manipulating the `test` function with monkey-patching and employing the useFakeTim

This particular setup is quite specific and after scouring the internet, I couldn't find any similar resources. Hence, I decided to post it here in case it proves helpful to someone.


While there are numerous inquiries regarding Jest and the error message Async callback was not invoked, none seem to address the core issue of utilizing jest.useFakeTimers(). When employing fake timers, my function should execute instantaneously, yet for some reason Jest seems to be hanging.

I am using Jest version 26 and have manually specified to use modern timers.

Below is a comprehensive code snippet that showcases the problem:

jest.useFakeTimers('modern')
let setTimeoutSpy = jest.spyOn(global, 'setTimeout')

async function retryThrowable(
  fn,
  maxRetries = 5,
  currentAttempt = 0
) {
  try {
    return await fn()
  } catch (e) {
    if (currentAttempt < maxRetries) {
      setTimeout(
        () => retryThrowable(fn, maxRetries, currentAttempt + 1),
        1 * Math.pow(1, currentAttempt)
      )
    }
    throw e
  }
}

describe('retryThrowable', () => {
  const fnErr = jest.fn(async () => { throw new Error('err') })

  it('retries `maxRetries` times if result is Err', async () => {
    jest.clearAllMocks()
    const maxRetries = 5

    await expect(retryThrowable(() => fnErr(), maxRetries)).rejects.toThrow('err')

    for (let _ in Array(maxRetries).fill(0)) {
      jest.runAllTimers()
      await Promise.resolve() // https://stackoverflow.com/a/52196951/3991555
    }

    expect(setTimeoutSpy).toHaveBeenCalledTimes(maxRetries)
  })
})

The complete error message reads as follows:

Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.

      at mapper (../../node_modules/jest-jasmine2/build/queueRunner.js:27:45)

Any input or suggestions would be greatly appreciated.


edit 1: I have attempted using --detectOpenHandles but it did not provide any additional insights.


edit 2: Upon testing the above code snippet in a fresh project, it passed successfully. Therefore, the root cause of the issue must lie elsewhere in my Jest configuration. I will update this post with a solution once I identify the exact source of the problem.

Answer №1

My problem was traced back to my jest setup.

In our testing environment, we run tests against an in-memory database and wrap each test within a DB transaction for cleanliness. Since Jest lacks a built-in aroundEach hook like other test runners, we had to patch the global test and it functions to execute the test callback within a transaction. Using Sequelize as our ORM added intricacies to handling transactions.

The specific test mentioned above involved calling setTimeout recursively with a function that threw errors or rejected promises. It turned out that Sequelize transactions do not handle uncaught rejections well, leading to the test hanging indefinitely. Despite successful rollback and execution of all test expectations, the test failed to exit.

Workaround #1 (imperfect)

To address this issue, I opted for a somewhat crude yet practical approach. I created a variation of the Jest test function that bypassed the monkey-patched behavior.

// jest.setup.ts
declare namespace jest {
  interface It {
    noDb: (name: string, fn?: ProvidesCallback, timeout?: number) => void
  }
}

it.noDb = it
// jest.config.js
module.exports = {
  // ...
  setupFilesAfterEnv: [
    './jest.setup.ts', // <-- inject `it.noDb` method
    './jest.mokey-patch.ts', // <-- monkey-patching
  ],
}

Subsequently, I updated the test from the original post to utilize this new function

it.noDb('retries `maxRetries` times if result is Err', ...

For a deeper dive into how and why this workaround functions, refer to this informative blog post.

Workaround #2 (preferred)

Further investigation led me to identify the core issue as unhandled promise rejections occurring on the main thread. Although the exact conflict with Sequelize Transactions remains unclear, it's generally best practice to avoid such scenarios.

To circumvent this problem entirely without resorting to unconventional Jest extensions, I revised the method to throw exceptions only on the initial call. This adjustment allows us to manage errors when invoking retryThrowable, but suppress subsequent error throwing.

  // ...
  try {
    return await fn()
  } catch (e) {
    if (currentAttempt < maxRetries) {
      setTimeout(
        () => retryThrowable(fn, maxRetries, currentAttempt + 1),
        1 * Math.pow(1, currentAttempt)
      )
    }

    // 💡 this is the new part
    if (currentAttempt === 0) {
      throw e
    }
  }
  // ...

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for capturing an error generated by a child component's setter?

I've created an App component that contains a value passed to a Child component using the @Input decorator. app.component.html <app-child [myVariable]="myVariable"></app-child> app.component.ts @Component(...) export class AppC ...

Issues with React Native imports not functioning properly following recent upgrade

Hey there, I’ve been tasked with updating an old React-Native iOS project from version 0.25.1 to 0.48.0. However, I’m encountering several compiler issues and struggling to navigate through the code updates. The project includes an index.ios.js file s ...

Tips for enhancing a search algorithm

I am currently working on implementing 4 dropdown multi-select filters in a row. My goal is to render these filters and update a new array with every selected option. Additionally, I need to toggle the 'selected' property in the current array of ...

Leveraging clusters in Node.js for REST API deployment

Within my node.js application, I have a REST API that contains complex logic with extensive looping, taking over 7 seconds to complete. As the loop count may increase in the future, the processing time is bound to increase as well. To optimize performance ...

What is the best way to transfer data from Vue.js to a PHP variable?

<section class="scans"> <h3>Scans</h3> <ul v-if="scans.length === 0"> <li class="empty">No scans yet</li> </ul> <transition-group name="scans" tag="ul"> <li v-for ...

Troubleshooting regex validation issues in a JSFiddle form

Link to JSFiddle I encountered an issue with JSFiddle and I am having trouble figuring out the root cause. My aim is to validate an input using a regex pattern for a person's name. $("document").ready(function() { function validateForm() { var ...

Tips for adding additional text to c3.js Regions

Is there a way to add text to c3.js regions? I've set up three specific regions in my chart and I'd like to attach unique text labels to each of them. My attempts with d3.js have not been successful. var rectOffset = (( d3.select(this).attr("x") ...

Issue with Ajax not sending query string to ASP.NET controller

I am currently working with an Ajax function that serializes data sent from my view into a query string. Here is the code snippet: UpdateFIConfig: function ($appForm) { var valid = $appForm.valid(); //if not valid the validate plugin will take ca ...

Unlocking the potential of input values in Angular.jsDiscovering the secret to

I'm currently experimenting with the angular date picker directive. My goal is to retrieve the entered date value from the date picker and log it to the console. However, all of my attempts so far have been unsuccessful. Here's a snippet of my c ...

Rotational orientation of a progress circle image in SVG

I am currently working on developing a circular progress bar, similar to the one shown in the image below. The progress of this bar is determined by percentages and it moves around the circle accordingly. I have managed to get the progression moving, b ...

Sending JSON-encoded data using HTML5 Server-Sent Events (SSE) is a

I have a script that triggers an SSE event to fetch json encoded data from online.php. After some research, I discovered methods for sending JSON data via SSE by adding line breaks. My question is how to send JSON through SSE when the JSON array is genera ...

Switch out a visual element for Dropzone.js

During my recent project, I utilized Dropzone.js for image uploads. However, I am now interested in transforming the dropzone area into an actual image. For instance, if there is a "featured image" attached to an article, users should be able to drag and ...

Avoiding HTML (JSX) tag duplication in React Component

Having the same code in my React component multiple times is becoming repetitive and inefficient. Is there a way to follow the DRY principle and avoid repeating this code? While creating a landing page with Sass styling, I noticed that I am duplicating t ...

.fetchevery(...).then has no function

I recently upgraded Angular to version 1.6.4. As a result, I made changes to the code by replacing .success and .error with .then However, now I am encountering the following error: An unexpected TypeError occurred: .getAll(...).then is not a function ...

What is the best way to ensure only one checkbox is checked at a time and have it automatically uncheck when another checkbox is selected?

Is there a way to make a checkbox automatically uncheck when another one is checked? For example, when checkbox one is clicked, it should be marked as checked and any other checkboxes should be marked as unchecked. The same behavior should apply when check ...

Managing extensive geographical information through HTTP

What is the most efficient way to transmit a substantial volume of map information via HTTP for rendering on the client side? There must be a more effective approach than simply transmitting a JSON file containing all map data. I am interested in how maj ...

Slide your finger upwards to shut down the mobile navbar in bootstrap

Hey there, I'm currently developing a website using Bootstrap framework v3.3.4. I have a mobile navbar that opens when I click a toggle button, but I would like it to slide up to close the navigation instead. Here is an image showing the issue Do y ...

What is the optimal placement for promises in Angular: Factory or Controller?

In my application, I have a basic factory to manage API calls. Currently, the structure looks like this: .factory('apiFactory', function($http){ var url = 'http://192.168.22.8:8001/api/v1/'; return { getReports: function() { ...

Using React Bootstrap to conditionally render columns within an array map

Within my React application, I am currently utilizing the map function to generate Bootstrap columns in the JSX code of the render method. One specific attribute within the array object I'm mapping is named "taken." Depending on whether this attribute ...

Difficulty with obtaining .responsetext in AJAX and PHP

On my real estate website, I have a form for users to 'Add a Property'. Although I will implement form validation later on, here is the current html/js code: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR ...