2015-03-03 4 views
5

Я хотел бы заглушить метод save, доступный моделям Mongoose. Вот пример модели:Stubbing метод сохранения мангуста на модели

/* model.js */ 
var mongoose = require('mongoose'); 
var userSchema = mongoose.Schema({ 
    username: { 
    type: String, 
    required: true 
    } 
}); 
var User = mongoose.model('User', userSchema); 
module.exports = User; 

У меня есть некоторые вспомогательные функции, которая будет вызывать метод save.

/* utils.js */ 
var User = require('./model'); 
module.exports = function(req, res) { 
    var username = req.body.username; 
    var user = new User({ username: username }); 
    user.save(function(err) { 
    if (err) return res.end(); 
    return res.sendStatus(201); 
    }); 
}; 

Я хотел бы проверить, что user.save вызывается внутри моей вспомогательной функции с помощью тестового блока.

/* test.js */ 
var mongoose = require('mongoose'); 
var createUser = require('./utils'); 
var userModel = require('./model'); 

it('should do what...', function(done) { 
    var req = { username: 'Andrew' }; 
    var res = { sendStatus: sinon.stub() }; 
    var saveStub = sinon.stub(mongoose.Model.prototype, 'save'); 
    saveStub.yields(null); 

    createUser(req, res); 

    // because `save` is asynchronous, it has proven necessary to place the 
    // expectations inside a setTimeout to run in the next turn of the event loop 
    setTimeout(function() { 
    expect(saveStub.called).to.equal(true); 
    expect(res.sendStatus.called).to.equal(true); 
    done(); 
    }, 0) 
}); 

var saveStub = sinon.stub(mongoose.Model.prototype, 'save') я обнаружил из here.

Все в порядке, если я не попытаюсь что-то добавить к моему saveStub, например. с saveStub.yields(null). Если бы я хотел, чтобы имитировать ошибка передается в save обратного вызова с saveStub.yields('mock error'), я получаю эту ошибку:

TypeError: Attempted to wrap undefined property undefined as function 

Трассировка стека совершенно бесполезно.

Исследование я сделал

Я попытался реорганизовать свою модель, чтобы получить доступ к базовой модели пользователя, в соответствии с рекомендациями here. Это дало мне такую ​​же ошибку. Вот мой код для этой попытки:

/* in model.js... */ 
var UserSchema = mongoose.model('User'); 
User._model = new UserSchema(); 

/* in test.js... */ 
var saveStub = sinon.stub(userModel._model, 'save'); 

Я обнаружил, что this solution не работает для меня вообще. Может быть, это потому, что я настраиваю свою модель пользователя по-другому?

Я также пробовал Mockery после this guide и this one, но это было гораздо более сложным, чем я думал, что это необходимо, и заставил меня подвергнуть сомнению ценность траты времени, чтобы изолировать db.

Мое впечатление, что все это имеет отношение к таинственному способу изготовления мангуста save. Я прочитал что-то об этом, используя npm hooks, что делает метод save скользкой вещью для заглушки.

Я также слышал о mockgoose, хотя я еще не пытался это решение. Кто-то добился успеха в этой стратегии? [EDIT: получается, что mockgoose предоставляет базу данных в памяти для простоты настройки/срыва, но это не решает проблему опечатывания.]

Любое понимание того, как решить эту проблему, было бы очень полезно.

ответ

2

Вот окончательная конфигурация я разработал, которая использует комбинацию Синона и насмешки:

// Dependencies 
var expect = require('chai').expect; 
var sinon = require('sinon'); 
var mockery = require('mockery'); 
var reloadStub = require('../../../spec/utils/reloadStub'); 

describe('UNIT: userController.js', function() { 

    var reportErrorStub; 
    var controller; 
    var userModel; 

    before(function() { 
    // mock the error reporter 
    mockery.enable({ 
     warnOnReplace: false, 
     warnOnUnregistered: false, 
     useCleanCache: true 
    }); 

    // load controller and model 
    controller = require('./userController'); 
    userModel = require('./userModel'); 
    }); 

    after(function() { 
    // disable mock after tests complete 
    mockery.disable(); 
    }); 

    describe('#createUser', function() { 
    var req; 
    var res; 
    var status; 
    var end; 
    var json; 

    // Stub `#save` for all these tests 
    before(function() { 
     sinon.stub(userModel.prototype, 'save'); 
    }); 

    // Stub out req and res 
    beforeEach(function() { 
     req = { 
     body: { 
      username: 'Andrew', 
      userID: 1 
     } 
     }; 

     status = sinon.stub(); 
     end = sinon.stub(); 
     json = sinon.stub(); 

     res = { status: status.returns({ end: end, json: json }) }; 
    }); 

    // Reset call count after each test 
    afterEach(function() { 
     userModel.prototype.save.reset(); 
    }); 

    // Restore after all tests finish 
    after(function() { 
     userModel.prototype.save.restore(); 
    }); 

    it('should call `User.save`', function(done) { 
     controller.createUser(req, res); 
     /** 
     * Since Mongoose's `new` is asynchronous, run our expectations on the 
     * next cycle of the event loop. 
     */ 
     setTimeout(function() { 
     expect(userModel.prototype.save.callCount).to.equal(1); 
     done(); 
     }, 0); 
    }); 
    } 
} 
+0

Это сработало для меня! Большое спасибо за обмен! <3 –

1

Вы пробовали:

sinon.stub(userModel.prototype, 'save') 

Кроме того, где функция помощника вызывался в тесте? Похоже, вы определяете функцию как модуль utils, но называете ее как метод объекта контроллера. Я предполагаю, что это не имеет ничего общего с этим сообщением об ошибке, но это затрудняло определение того, когда и где был вызван заглушка.

+0

Спасибо за ответ. Спасибо за уловку. Разница в определении функции в качестве модуля и последующем ее вызове в качестве метода была ошибкой, возникающей при очистке кода и упрощении его для SO. Я исправил это сейчас. – AndrewSouthpaw

+0

Я попытался использовать 'sinon.stub (userModel.prototype, 'save')'. Он точно фиксирует поведение метода 'save', но он все еще бросает описанный выше тип TypeError. – AndrewSouthpaw

Смежные вопросы