Search code examples
javascriptnode.jsmocha.jses6-promisesupertest

Unhandled Promise rejection warning: TypeError: First argument must be a string or Buffer


Question seems to be a duplicate but I have been working to solve this from last 3 hours. Basically I'm using supertest & mocha to test my API. I'm unable to understand which promise is not getting resolved.

app.post('/todos', (req, res) => {
 var todo = new Todo({
 text : req.body.text
});
 todo.save().then( (doc) => {
  res.status(200).send(doc)
  }, (e) => {
  res.status(400).end(e);
 });
});

Following is the test I have written:

const expect = require('expect');
const request = require('supertest');

var {app} = require('./../server');
var {Todo} = require('./../models/todo');

// Setup database 
beforeEach((done) => {
  Todo.remove({})
 .then(() => done())
 .catch((e)=> done(e));
}); 

describe('Post /todos', () => {
it('should create a new todo', (done) => {

var text = 'Testing text';

// supertest to get, post
request(app)
  .post('/todos')
  .send({text}) // Automatic conversion to json - ES6
  .expect(200) // assertions
  .expect((res) => { // custom parsing
    expect(res.body.text).toBe(text);
  })
  .end((err, res) => {  // done directly accepted, but rather let's be more precise now - check mongodb
    if (err) {
      return done(err);
    }
    Todo.find()
      .then((todos) => {
        expect(todos.length).toBe(1);
        expect(todos[0].text).toBe(text);
        done();
      })
      .catch((e) => done(e));
  });
 });
});

Please help to resolve this. Here's the whole error msg:

mocha server/**/*.test.js Listening on port: 3000 Post /todos (node:1882) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: First argument must be a string or Buffer (node:1882) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. 1) should create a new todo 0 passing (2s) 1 failing 1) Post /todos should create a new todo: Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.


Solution

  • The end function in express only accepts a string or a buffer, not an object (see https://expressjs.com/en/api.html#res.end and https://nodejs.org/api/http.html#http_response_end_data_encoding_callback).

    However, it looks like todo.save() calls reject with an object, meaning that e causes the TypeError. Because this is an error with catching a promise rejection, the error is wrapped in the Unhandled Promise Rejection warning.

    Assuming the e is { message: "First argument must be a string or Buffer" }, the new code could be:

     todo.save().then( (doc) => {
      res.status(200).send(doc)
      }, (e) => {
      res.status(400).end(e.message);
     });
    

    The reason that the todo.save() promise is being rejected is probably because the text in the todo appears to be undefined. This is the JSON that is assigned to req:

    {
      text: 'Testing text'
    }
    

    However, it should be this:

    {
      body: {
        text: 'Testing text'
      }
    }
    

    The testshould then be fixed if you change the send to this:

    .send({ body: { text }) // Automatic conversion to json - ES6