Supertest and Mongoose Middleware (post remove)

1k views Asked by At

I have been fiddling with this for days, and I cannot figure out why the Mongoose middleware is not being invoked.

So I have an API in node.js and I have a website using Angular.js. The Mongoose middleware is this:

schema.post('remove', function (doc) {
    console.log('doctors - post - remove');
}); 

So this hook is called perfectly fine when invoked from the Angular front end. However, when I run a test with supertest, chai, and mocha the hook is not invoked. Here is my code for the testing:

it('/doctors - POST - (create doctor)', function(done){
  request(app)
    .post('/doctors')
    .send(doctor)
    .end(function (err, res){
      if (res.body['error']) {
        expect(S(res.body['error']).startsWith('doctor already exists')).to.be.true;
      }
      else
        expect(res.body['email']).to.equal(doctor['email']);
      done();
    });
});

....

it('/doctors/remove - DELETE', function(done){
  request(app)
    .del('/doctors/remove')
    .auth(new_doctor_creds["email"], new_doctor_creds["pass"])
    .end(function (err, res){
      expect(Object.keys(res.body).length).to.not.equal(0);
      done();
    });
});

And here is my route for the express app:

app.delete('/doctors/remove', authController.isAuthenticated, function (req, res, next) {
    var email = req.user['email'];
    Doctors.findOne({email:email}).remove(function (err, removed) {
        if (err) return next(err);

        return res.status(200).send(removed);
    });
});

Again, this Mongoose middleware works perfectly fine when invoked from an API call from the Angular app. However, it does not work when tested with supertest. Any ideas on what to do here?


EDIT: I tried to recreate this example with a simplified version that way you can see all of the code. So here is a two file version that is STILL not working. Here is the app.js:

var mongoose = require('mongoose');
var app = require('express')();
var http = require('http');
var fs = require('fs');
var Doctors = require('./schema');

mongoose.connect('mongodb://localhost/m4', function(err) {
    if (err) throw err;
    console.log('connected');

    app.get('/post', function (req, res, next) {
        console.log('create');
        Doctors.create({email:"hello"}, function (err, inserted) {
            if (err) console.log(err);

            res.end();
        });
    });

    app.get('/delete', function (req, res, next) {
        console.log('removed');
        Doctors.remove({email:"hello"}, function (err, removed) {
            if (err) console.log(err);

            res.end();
        });
    });

    http.createServer(app).listen('6000', function () {
        console.log('now listen on localhost:6000');
    });
});

and the schema:

var mongoose = require('mongoose');

var schema = mongoose.Schema({
    email: { type: String }
});

schema.pre('save', function (next) {
    console.log('doctors - post - save');
    next();
});

schema.post('remove', function (doc) {
    console.log('doctors - post - remove');
});

module.exports = mongoose.model('Doctors', schema);
2

There are 2 answers

6
JME On BEST ANSWER

Here's what I suggest. Let's perform the #remove on the doc found by #findOne. If I remember correctly, remove post hooks only works on Doc#remove and not on Model#remove.

schema.post('remove', function (doc) {
    console.log('doctors - post - remove'); // <-- now runs
});

app.delete('/doctors/remove', authController.isAuthenticated, function (req, res, next) {
    var email = req.user['email'];
    Doctors.findOne({email: email}, function(err, doc) {
      if (err) {
        return next(err);
      }

      doc.remove().then(function(removed) {
        return res.status(200).send(removed);
      }, function(err) {
        next(err);
      });

    });
});
0
Diego Haz On

Mongoose post hooks run AFTER the operation is completed, concurrently with operation callbacks. See the comments below:

Doctors.findOne({email:email}).remove(function (err, removed) {
    // All this code and the post hook are executed at the same time
    if (err) return next(err);

    // Here you send the response so supertest#end() will be triggered
    // It's not guaranteed that post remove was executed completely at this point
    return res.status(200).send(removed);
});

Post hooks were made to run processes independent of the server response. When you run tests, the server shuts down right after the tests are completed, and maybe it had no time enough to finish the post hooks. In the other hand, when you call the API from a client, normally you keep the server running, so the post jobs can be completed.

Now, there comes a problem: how can we test post hooks consistently? I got up this question because I was looking for a solution to that. If you already have an answer, please post here.