Creating a Mocha+Chai test that anticipates an exception being thrown by setTimeout

Here is what I have:

it('invalid use', () => {
  Matcher(1).case(1, () => {});
});

I am trying to ensure that the case method throws an exception after a delay. How can I specify this for Mocha/Chai so that the test passes only if an exception is thrown (and should fail otherwise)?

I need to emphasize that the case method cannot be modified in any way.

To simulate the behavior for testing purposes, consider the following equivalent code:

it('setTimeout throw', _ => {
  setTimeout(() => { throw new Error(); }, 1); // This line must remain unchanged
});

I attempted the following approach:

it('invalid use', done => {
  Matcher(1).case(1, () => {});
  // Call the done callback after 'case' may throw an error
  setTimeout(() => done(), MatcherConfig.execCheckTimeout + 10);
});

However, this solution is not helping me as it produces opposite results - passing when no exception is thrown from case (setTimeout) and failing when an exception is thrown.

I remember reading about utilizing a global error handler, but I prefer to find a clean solution using Mocha and/or Chai, if possible (I assume Mocha already incorporates something similar behind the scenes).

Answer №1

Handling exceptions within an asynchronous callback can be tricky, as discussed in this Stack Overflow thread. The execution model of ECMAScript plays a role in this limitation. One workaround is to implement global error handling specific to your environment, such as using

process.on('uncaughtException', ...)
in Node.js.

If you convert your function to Promises, you can simplify testing by utilizing the Chai plugin called chai-as-promsied:

import * as chai from 'chai';

import chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;

it('invalid use', async () => {
  await expect(Matcher(1).case(1, () => {})).to.eventually.be.rejected;
});

Answer №2

To make Mocha statements such as before, after, or it work asynchronously, you can utilize promises. Below is an example of how to implement async tests with promises. Remember to set a timeout using this.timeout(...) if the async function may take more than 2 seconds to complete.

it('some test', () => {
    return new Promise(function(resolve,reject){
        SomeAsyncFunction(function(error,vals) {
            if(error) {
                 return reject(error);    
            } else {
                try {
                    //perform chai tests here
                } catch(e) {
                    return reject(e);
                }
                return resolve();
            }
        });
    });
});

In your specific case, where you expect an error to be thrown after a certain time period, you can structure your test like this:

it('invalid use', () => {
    let prom = new Promise(function(resolve,reject){
        Matcher(1).case(1, () => {return reject(new Error('did not throw'))});
    });
    prom.catch(function(err){
        try {
            expect(err).to.be.an('error');
            expect(err.message).to.not.equal('did not throw');
            //additional checks to verify the error
        } catch(e) {
            return Promise.reject(e);
        }
        return Promise.resolve();
    });
    return prom;
});

Answer №3

According to information from the Chai documentation:

When no arguments are provided, .throw will execute the target function and verify that it throws an error.

For example, you could do something like this:

expect(Matcher(1).case(1, () => {})).to.throw

Answer №4

If the code being tested includes a call to setTimeout with a callback that throws an uncaught exception, then:

1) the code is considered broken

2) one way to identify this issue is through a platform global exception handler such as process.on('uncaughtException', as suggested by user ComFreek

If all else fails, you can temporarily replace setTimeout during testing (e.g., using sinon.stub) or manually stub it.

When using a stubbed setTimeout, you can modify the timeout handler to detect exceptions and trigger appropriate assertions.

It's important to note that this approach should be a last resort - ideally, your application code should handle errors properly to ensure smooth operation, not just for testing purposes but for overall code quality.

Here is some pseudocode as an example:

it('test', (done) => {

    const originalSetTimeout = setTimeout;
    setTimeout = (callback, timeout) => {
        originalSetTimeout(() => {
            try {
                callback();
            } catch(error) {
                 // Exception intercepted in _SOME_ setTimeout handler
            }
        }, timeout)
    }
    yourTestCodeThatTriggersErrorInSomeSetTimeoutCallback(done);
})

Please note that I have deliberately omitted proper asynchronous cleanup code. This is left as an exercise for the reader. Check out sinon.js and its sandbox feature for assistance.

Keep in mind that this approach will intercept all setTimeout calls during the test duration. Proceed with caution!

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

How can I deactivate a Material UI button after it has been clicked once?

Looking to make a button disabled after one click in my React project that utilizes the MUI CSS framework. How can I achieve this functionality? <Button variant="contained" onClick={()=>handleAdd(course)} disabled={isDisabled} > ...

Having trouble establishing a connection to the FTP server through the "ftp" package provided by npm

Attempting to establish a connection to a Secured FTP server using the "ftp" package. When connecting to an unsecured server, everything functions as expected with all events firing and content being displayed. However, upon trying to connect to a server ...

Is your Cloud Functions task generating an Array when querying?

To access items and products in my database, I need to retrieve the "ean" field from the product and check if it matches the one in the request body. The structure of my database is as follows: "cart": { "items": { "0": {info here}, "1": {info ...

Node.js and Express: accessing req.body yields undefined value

In the midst of creating a basic browser application using Express, I encountered an issue when attempting to retrieve the value selected by a user from a dropdown menu. I assigned individual values to each option and set the form method to /post. However, ...

AngularJS - Ensuring the <script> tag is included only after all directives have been rendered

Forgive me if this question has been asked before. I've spent quite some time looking for a solution to no avail. I'm in the process of converting an existing application, which relies heavily on jQuery, to utilize AngularJS. However, I've ...

Tips on ending an interval in rxjs once it has started

Implemented a code in an Angular component to retrieve data from a service every 10 seconds on initialization. Now, I need to find a way to stop the interval after a certain period of time such as 5 minutes or when all the necessary data has been collected ...

Fetching Data Using Cross-Domain Ajax Request

Seeking assistance with a cross-domain Get request via Ajax. The code for my ajax request is as follows: var currency_path = "http://forex.cbm.gov.mm/api/latest"; $.ajax({ url: currency_path, crossDomain:true, type:"GET", dataTyp ...

Error suddenly appeared when trying to serve a previously functional project locally: Firebase function module not found

After not making any changes to my firebase-related files, I suddenly started encountering the following issue that I just can't seem to figure out: We were unable to load your functions code. (see above) - It appears your code is written in Types ...

Obtain decrypted information from the token

I am facing difficulty in retrieving decrypted user data for the current user. Every time a user logs in, they receive a token. After logging in, I can take a photo and send it to the server. Looking at my code, you can see that this request requires a to ...

Sails JS - Flash message display issue on Heroku in production environment, works smoothly in development mode

I'm experiencing an issue with the flash message on my local machine during development, as it works fine there but not when I deploy the app on Heroku. I've been searching for a solution without any luck so far. api/policies/flash.js module.ex ...

I possess both a minimum and maximum number; how can I effectively create an array containing n random numbers within

Given a minimum number of 10.5 and a maximum number of 29.75, the task is to generate an array within these two ranges with a specific length denoted by 'n'. While the function for generating the array is provided below, it is important to calcul ...

How will the presence of @types/react impact the higher-level files in my project?

https://i.sstatic.net/TfsLf.png https://i.sstatic.net/RqmMS.png Here is the structure of my directories vue node_modules src react_app node_modules @types/react package.json ...other file package.json Why does the presenc ...

implementing dynamic navigation bar, when transforming into a dropdown menu using ReactJS

I am currently utilizing a navigation bar from a Bootstrap theme within my React project. The navigation bar includes an in-built media query that determines whether it should display as a dropdown menu or not, with a toggler to handle the dropdown functi ...

What is the most efficient method for exporting Express route functions for promise chaining?

Currently, I am in the process of refactoring an API route to utilize ES6 promises instead of callbacks, so as to avoid callback hell. Upon successful conversion to a promise chain, my intention was to extract the .then() functions into a separate file fo ...

Issue: Trouble with Rotating Tooltips in Javascript

I am facing a challenge with the tooltips on my website. I want to ensure that all tooltips have a consistent look and transition effects, but I am struggling to achieve this. The rotation and other effects applied using javascript are not functioning prop ...

Change the div attribute when clicking on a corresponding link

For the full code, please visit: https://plnkr.co/edit/6TTLVcsXLV7C1qXSMQV0?p=preview Here is an angular ui bootstrap accordion with nested panels: <uib-accordion close-others="oneAtATime"> <div ng-repeat="sub in subdivisions"> < ...

HTML control for adjusting the pan and tilt of a device

I'm looking to develop an interactive input where users can drag a dot within a box and see the 2D coordinates of that dot displayed. The goal is to use this feature to control pan and tilt values. A similar functionality from another application is s ...

Running an express server on localhost with nginx: A step-by-step guide

Is it possible to run the express server using Nginx on localhost? And if so, how can this be accomplished? const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => res.send('Hello ...

What is the best way to retrieve the "name" and "ObjectId" properties from this array of objects? (Using Mongoose and MongoDB)

When trying to access the name property, I encountered an issue where it returned undefined: Category.find() .select("-_id") .select("-__v") .then((categories) => { let creator = req.userId; console.log(categories.name) //unde ...

Reorganizing JSON Information

Currently, I am dealing with a JSON file that contains multiple sets of data structured like this: {"name": ["Adelphi University"], "supp": ["Yes: E, WS"], "ed": ["\u00a0"], "online": ["$40"], "ea": ["12/1"], "mid": ["No"], "rd": ["Rolling"], "recs": ...