The Sinon stub appears to be completely ignored during the test, despite its successful use in earlier tests

I am currently testing the functionality of an express router using sinon. The first test in my code below passes without any issues, but I'm having trouble with the second test. It's not passing and I can't seem to figure out why.

When I send an http request to the route, it works as expected.

There seems to be some issue with the catch block causing the problem. Below is the code snippet that I have narrowed it down to, along with the error message:

books.js

import express from 'express';
import models from '../db/models';
const router = express.Router();

var indexPost = async (req, res, next) => {
  try {
    let savedBook = await models.Book.create({
      title: req.body.title || null,
      isbn: req.body.isbn || null,
      author: req.body.author || null
    });
    res.status(201).json({ book: savedBook.id });
  } catch (err) {
    res.status(400).send('');
  }
};
router.post('/', indexPost);

export default router;
export { indexPost };

books.test.js

import { indexPost } from '../../../src/routes/books';
import models from '../../../src/db/models';
import sinon from 'sinon';
import { expect } from 'chai';
import sinonTestFactory from 'sinon-test';

const sinonTest = sinonTestFactory(sinon);

describe('Books router', () => {
  describe('indexPost', () => {
    it('should save the book to the database', sinonTest(async function () {
      let req = {
        body: {
          title: 'Book Title',
          isbn: '123asera23',
          author: 123
        }
      };

      let res = {
        status: status => {},
        json: json => {}
      };

      this.stub(res, 'status').returns(res);
      this.stub(res, 'json').returns(res);

      indexPost(req, res);

      let book = await models.Key.findById(1);

      expect(book.title).to.equal('Book Title');
      expect(book.isbn).to.equal('123asera23');
      expect(book.author).to.equal(123);

      sinon.assert.calledWith(res.status, 201);
      sinon.assert.calledWith(res.json, { book: 1 });
    }));

    it('should throw an error if data is not all there', sinonTest(async function () {
      let req = {
        body: {
          title: 'Book Title',
          author: 123
        }
      };

      let res = {
        status: status => {},
        send: send => {}
      };

      this.stub(res, 'status').returns(res);
      this.stub(res, 'send').returns(res);

      indexPost(req, res);

      sinon.assert.calledWith(res.status, 400);
      sinon.assert.calledWith(res.send, '');
    }));
  });
});

Error

1) Books router
    indexPost
        should throw an error if data is not all there:
            AssertError: expected status to be called with arguments
            at Object.fail (/var/app/node_modules/sinon/lib/sinon/assert.js:96:21)
            at failAssertion (/var/app/node_modules/sinon/lib/sinon/assert.js:55:16)
            at Object.assert.(anonymous function) [as calledWith] (/var/app/node_modules/sinon/lib/sinon/assert.js:80:13)
            at Context.<anonymous> (tests/src/routes/books.test.js:58:20)
            at Generator.next (<anonymous>)
            at step (tests/src/routes/books.test.js:21:191)
            at tests/src/routes/keys.test.js:21:437
            at new Promise (<anonymous>)
            at Context.<anonymous> (tests/src/routes/books.test.js:21:99)
            at callSandboxedFn (/var/app/node_modules/sinon-test/lib/test.js:94:25)
            at Context.sinonSandboxedTest (/var/app/node_modules/sinon-test/lib/test.js:114:24)

Answer №1

Upon realizing that the solution was not as straightforward as initially thought, I took the time to figure out the correct answer.

The issue stemmed from my incorrect approach to handling non-async code within JavaScript.

I needed to ensure that my controller returned a promise to Mocha's it function. To achieve this, the controller itself had to return a promise, especially when dealing with database operations where the database call itself could be returned as a promise.

After the promise resolves with either resolve or reject, assertions can be made to verify the success of the tests.

The key lies in chaining promises all the way from the bottom of the controller back up to Mocha's it function.

Below is the revised code to address this:

import express from 'express';
import models from '../db/models';
const router = express.Router();

var indexPost = (req, res, next) => {
  return models.Book.create({
    title: req.body.title || null,
    isbn: req.body.isbn || null,
    author: req.body.author || null
  }).then(savedBook => {
    res.status(201).json({ book: savedBook.id });
  }).catch(err => {
    res.status(400).send('');
  });
};
router.post('/', indexPost);

export default router;
export { indexPost };

import { indexPost } from '../../../src/routes/books';
import models from '../../../src/db/models';
import sinon from 'sinon';
import { expect } from 'chai';

describe('Books router', () => {
  describe('indexPost', () => {
    it('should save the book to the database', async () => {
      let req = {
        body: {
          title: 'Book Title',
          isbn: '123asera23',
          author: 123
        }
      };

      const jsonStub = sinon.stub()
      const res = { status: status => ({ json: jsonStub, send: err => err }) }
      const statusSpy = sinon.spy(res, 'status')

      return indexPost(req, res).then(() => {
        let book = await models.Key.findById(1);

        expect(book.title).to.equal('Book Title');
        expect(book.isbn).to.equal('123asera23');
        expect(book.author).to.equal(123);

        sinon.assert.calledWith(res.status, 201);
        sinon.assert.calledWith(res.json, { book: 1 });
      })
    }));

    it('should throw an error if data is not all there', () => {
      let req = {
        body: {
          title: 'Book Title',
          author: 123
        }
      };

      const sendStub = sinon.stub()
      const res = { status: status => ({ json: err => err, send: sendStub }) }
      const statusSpy = sinon.spy(res, 'status')

      indexPost(req, res).then(() => {
        sinon.assert.calledWith(res.status, 400);
        sinon.assert.calledWith(res.send, '');
      });
    }));
  });
});

The response object quirks have also been resolved in the updated code above.

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

Utilizing PHP for Long Polling within AJAXcreateUrlEncodeProtect(chr(

Utilizing AJAX to refresh specific parts of a page without the need for constant reloading. However, I aim for the table to only refresh upon detecting changes (a concept known as long polling). Despite attempting to implement loops with break statements, ...

The Typewriter Effect does not appear alongside the heading

For my portfolio website, I am using React to create a unique typewriter effect showcasing some of my hobbies. Currently, the code is set up like this: "I like to" [hobbies] However, I want it to display like this: "I like to" [h ...

Issue: The system is unable to locate the module titled '@kyleshockey/object-assign-deep'

Recently, I installed the accept-language npm module. When attempting to start my project afterwards, an error message appeared: Error: Cannot find module '@kyleshockey/object-assign-deep' What steps can I take to fix this problem? ...

Obtain the complete shipping address with the 'addressLines' included using Apple Pay

Currently working on integrating Apple Pay WEB using JS and Braintree as the payment provider. In order to calculate US sales tax for the order, I need to gather certain information. The user initiates the payment process by clicking on the "Pay with Appl ...

Guide to integrating the Braintree API with Node.js

I am trying to implement Braintree for website payment gateway, but I encountered an issue while following the online guidelines. The code seems to be incompatible with Node.js. Am I missing something? Index.js: //send token to clients app.get("/client_t ...

Removing an element from an array in a Laravel database using AngularJS

I am currently working on a project where I need to delete an item from my Laravel database, which is a simple Todo-List application. To achieve this, I have implemented a TodosController in my main.js file using AngularJS and created a Rest API to connect ...

Using Vue.js, execute a function when there is input, but with a slight delay

Within my input field, I have implemented a method called activate triggered by v-on:input. This method is structured as follows: export default: { data() { return { isHidden: true } }, methods: { activate() ...

Building a Collapseable and Non-Collapseable Bootstrap4 NavBar in ReactJS

Is there an easy solution for creating a collapsible horizontal NavBar? <Navbar inverse fixedTop fluid collapseOnSelect> <Navbar.Header> <Navbar.Toggle /> </Navbar.Header> <Navbar.Collapse> <Nav> ...

Capture the incoming path and direct it back after the user logs in

I'm currently facing a challenge with my app that has a login feature and a dashboard with threads. Each thread has its own unique URL address. The issue arises when I want to share the thread URL with someone who is not logged in, as they would need ...

Issues with looping through JSON data retrieved from a request

Today has been a battle with this code, as I tirelessly search for a solution. I've been using the fetch API to retrieve values and I can successfully iterate through the first JSON to grab the ID from the album section. Now, my goal is to use a loop ...

Is there a way to cycle through each element within a loop and output a corresponding item from another array?

My data structure consists of an array of objects like this: arrOfObjs = [{type: "flower", egg: "something"}, {type: "flower", egg: "something2"}, {type: "notFlower", egg: "something3"}, {type: &q ...

Handle changes to input values on ng-click even if they were made outside of Angular's control

Looking to pass input values on ng-click even when they are changed outside of Angular. Everything works fine when manually typing, but once the inputs have dynamic values, ng-click passes empty form data. Below is the HTML code snippet: <form id="form ...

What is the most effective method for testing event emitters?

Imagine I have a basic component structured like this: @Component({ selector: 'my-test', template: '<div></div>' }) export class test { @Output selected: EventEmitter<string> = new EventEmitter<string>() ...

Ways to keep my information current in relation to JSON updates

I have successfully created a web application that retrieves data (JSON) from the ebay API and displays it on a chart illustrating the price of each item. The current setup is working well. However, I am eager to implement real-time updates on the chart w ...

Typescript - unexpected behavior when using imported JavaScript types:

I am struggling with headaches trying to integrate an automatically generated JavaScript library into TypeScript... I have packaged the JavaScript library and d.ts file into an npm package, installed the npm package, and the typings modules in the TypeScr ...

What are the techniques for implementing an if statement in CSS or resolving it through JavaScript?

Demo available at the bottom of the page Within my code, there is a div called #myDiv that defaults to an opacity of 0. Upon hovering over #myDiv, the opacity changes to 1. See the CSS snippet below: #myDiv { opacity: 0; } #myDiv:hover { opacity: 1 ...

Strategies for consistently receiving updates of Iframe content body within a react useEffect hook

When loading html into an iframe using srcDoc with the sandbox="allow-same-origin", I face a challenge. Despite the content displaying properly, the property frameRef.contentDocument.body.innerHTML remains empty. This issue persists even after s ...

Having problems with CakePHP validation in the view?

Welcome to my first post on this platform! I've been searching for a solution but haven't found anything that works for me yet. I'm relatively new to PHP and CakePHP. Yesterday, I managed to get everything set up without any issues and was ...

Error encountered in Selenium Webdriver: Element is not currently in view

I encountered an issue when trying to click on a button using the same type of code that worked fine for selecting a drop down. The error message I received was "Element is not currently visible and so may not be interacted with". Command duration or timeo ...

Tips and tricks for displaying JSON data in Angular 2.4.10

In my Angular project, I am facing an issue while trying to display specific JSON data instead of the entire JSON object. Scenario 1 : import { Component, OnInit } from '@angular/core'; import { HttpService } from 'app/http.service'; ...