Sinon - observing a spy that remains inactive, yet the test proceeds to enter the function

Having some trouble with a test function that uses two stubs. The stubs seem to be working fine, as I can see the spy objects inside when I console.log res.json or next. However, the spy is not being called when I make the assertion. The error message reads "expected spy to have been called at least once but was never called". The program itself works, but I'm struggling with this test. Can anyone provide assistance?

Here is the function in question:

export function createOrUpdateToken(req, res, next) {
  const { code, redirect_uri, realm_id, quickbooksAuth } = req.body;

  if (!code || !redirect_uri || !realm_id) {
    const message = 'Authorization code, Redirect URI and Realm Id are required';
    return next(new CustomError('Bad Request', message, 400));
  }
  return issueRefreshTokenBasedOnAuthorizationCode(
    {
      redirect_uri,
      authorizationCode: code,
      authorization: quickbooksAuth,
    })
    .then((response) => {
      const { body: { refresh_token } } = response;
      return securityModel.findOneAndUpdate(
        { realmId: realm_id },
        { refreshToken: refresh_token },
        { upsert: true },
        (err) => {
          if (err) return next(err);
          return res.json('Authentication successfull');
        });
    })
    .catch((err) => {
      return next(err);
    });
}

Here is the corresponding test:

it('Should create or update token', () => {
  req.body = {
    code: '1234',
    redirect_uri: 'www.test.com',
    realm_id: '12345',
    quickbooksAuth: 'dhajksdas.dsajdosaiudjsa.dsaojpdas'
  };
  sinon
    .stub(intuit, 'issueRefreshTokenBasedOnAuthorizationCode')
    .resolves({
      body: {
        refresh_token: 'hjdklasdashda.dsa.dasdsa.dasddasdas'
      }
    });

  sinon
    .stub(securityModel, 'findOneAndUpdate')
    .withArgs({ realmId: req.body.realm_id },
      { refreshToken: 'hjdklasdashda.dsa.dasdsa.dasddasdas' })
    .yields(null);
  createOrUpdateToken(req, res, next);
  sinon.assert.called(res.json);
});

Here are the conditions set for the test:

beforeEach(() => {
      res = {
        json: sinon.spy()
      };
      next = sinon.spy();
    });

    afterEach(() => {
      sinon.restore();
    })

Answer №1

Problem

The function createOrUpdateToken is running asynchronous code that is not complete before sinon.assert.called(res.json) is called, resulting in a failed test.

Resolution

Since createOrUpdateToken already returns a Promise, modify your test to be an async function and use await to wait for the Promise to resolve before making the assertion:

it('Should create or update token', async () => {  // make the test function async
  req.body = {
    code: '1234',
    redirect_uri: 'www.test.com',
    realm_id: '12345',
    quickbooksAuth: 'dhajksdas.dsajdosaiudjsa.dsaojpdas'
  };
  sinon
    .stub(intuit, 'issueRefreshTokenBasedOnAuthorizationCode')
    .resolves({
      body: {
        refresh_token: 'hjdklasdashda.dsa.dasdsa.dasddasdas'
      }
    });

  sinon
    .stub(securityModel, 'findOneAndUpdate')
    .withArgs({ realmId: req.body.realm_id },
      { refreshToken: 'hjdklasdashda.dsa.dasdsa.dasddasdas' })
    .yields(null);
  await createOrUpdateToken(req, res, next);  // await the returned Promise
  sinon.assert.called(res.json);  // SUCCESS
});

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

What is the best way to retrieve an accurately matched array?

I am working on a function that analyzes a string of DNA and should return an accurately matched DNA array. Here is the code snippet I have experimented with: function checkDNA(dna) { var dnaarr = []; for(var i = 0; i < dna.length; i++) { ...

Leveraging TypeScript unions within functions to handle and throw errors

As a newcomer to TypeScript, I've encountered an odd error that I need help with. I have various objects sending data to the server and receiving fresh data back of the same object type. These objects use a shared method for sending the data, so I ap ...

nodejs express routing issue resulting in not found error

I recently started a new node project and wanted to enhance my route adding capabilities. In the past, I only went one level deep with folders, but this time I wanted to go further. To achieve this, I created a recursive function that adds routes and navig ...

JavaScript believes that the function is not defined, despite its clear existence

This question pertains to an issue regarding the recognition of Bookshelf.js model function as a function. The error message "Function is undefined, Bookshelf.js model function is not being recognized as a function" arises when trying to POST to the login ...

Creating a node server that will intercept all requests from an Angular frontend (using http) and route them to a

Currently, I am utilizing express on node for routing and incorporating Angular as the front-end framework. Additionally, Redis is being used for session management. My objective is to ensure that whenever an HTTP request is made from Angular, it first goe ...

The canDeactivate function in the Angular 2 router can modify the router state even if I choose to cancel an action in the confirmation popup

In my Angular 2 project, I have implemented the canDeactivate method to prevent browser navigation. When a user tries to leave the page, a confirmation popup is displayed. However, if the user chooses to cancel, the router still changes its state even th ...

How to utilize the async pipe on an observable<Object> and connect it to a local variable in the HTML using Angular

Hey there! So, I have this observable called user$ which has a bunch of properties such as name, title, and address. component{ user$:Observable<User>; constructor(private userService:UserService){ this.user$ = this.userService.someMethodRet ...

To search for specific data in a Mongoose schema by specifying an attribute of type ObjectId

Here are the schemas I am working with: UserLike const UserLikeSchema = Schema({ user: { type: Schema.Types.ObjectId, ref: "User", required: [true, "User is required"], }, game: { type: Schema.Types.ObjectId, ...

Having trouble setting up WebpackDevMiddleware on a Windows machine

I've been struggling with this issue all day and still haven't found a solution. The error message I'm getting is: project\node_modules\webpack-dev-middleware\lib\context.js:95 context.compiler.hooks.invalid.tap(' ...

Utilizing PrimeNG menu items to bind commands to a base class function

I'm struggling to connect a parent class function with my Angular 2 PrimeNG menu options. HTML <p-menu #menu popup="popup" [model]="exportItems"></p-menu> <button type="button" class="fa fa-download" title="Export As" (click)="menu.to ...

Defining a structure for an entity in which its attributes distinguish between different data types and an array combination

I strongly believe that the best way to showcase this concept is through a clear example: enum EventType { A, B, C }; type MyEvent = [EventType.A, number] | [EventType.B, string] | [EventType.C, number, string]; const eventsGrouped: Record<Event ...

Utilizing Angular 2 for a dynamic Google Map experience with numerous markers

I am currently working on an Angular2 project that involves integrating Google Maps. My goal is to display multiple markers around a specific area on the map. Although I have been able to get the map running, I am facing issues with displaying the markers ...

Is it possible to skip backend validation when using the "please confirm by typing" delete pattern?

What are your thoughts on a specific situation involving frontend and backend validation? The consensus online is that both frontend and backend validation should be present for REST APIs. In my case, I am developing a modal that asks the user to input th ...

Error: Attempting to access the 'email' property of an undefined variable is not allowed

router.post('/login', async (res, req) => { const{error} = loginValidation(req.body); if (error) return res.status(400).send(error.details[0].message); //user validation const user = await User.findOne({ email: req.body.em ...

Error message: WebStorm shows that the argument type {providedIn: "root"} cannot be assigned to the parameter type {providedIn: Type<any> | "root" | null} and InjectableProvider

Transitioning my app from Angular v5 to v6 has presented me with a TypeScript error when trying to define providedIn in my providers. The argument type {providedIn: "root"} cannot be assigned to the parameter type {providedIn: Type | "root" | null} & ...

Implementing the "$store" property within Vue components

Click here for a guide on how to type the $store property. Unfortunately, I've been encountering issues with it. In my Vue 2 project created using vue-cliI, I included a vuex.d.ts file in ./src directory but the $store property in my components still ...

Node.js encountered an error: Address already in use. The specified port 8080 is currently being utilized by another application

My application was not functioning properly, and upon running the command "npm start", an error was thrown: Error: listen EADDRINUSE: address already in use :8080 Even after restarting my EC2 instance and attempting the command again, I encount ...

Tips for uploading files with angular and express js

Despite attempting to upload the files using middleware, I have been unsuccessful in retrieving the value from any of req.files, req.file, or req.body. ...

serving root URLs with express.static

When using express.static, it handles the root URL request. For example, if I want to redirect from https://example.com to https://example.com/dashboard in Express, there can be some unexpected behaviors as seen below. Case 1 (working) app.get('/&ap ...

Sending form data to the server using JavaScript: A step-by-step guide

Currently, I am dealing with the configuration of two servers. One server is running on React at address http://localhost:3000/contact, while the other is powered by Express at address http://localhost:5000/. My goal is to transmit form data as an object v ...