简体   繁体   中英

unit test node.js mongoose mocha chai sinon

I'm new to unit testing. My Web API project is MVC base on node-express-mongoose.

I have a conroller.js as following:

const getComments = async (req, res) => {
  let query = {};

  try {
    query = req.query.filter ? { email: new RegExp(`.*${req.query.filter}.*`, 'i') } : query;
    const comments = await util.getComments(query);
    return res.json(comments);
  } catch (err) {
    return res.status(500).send(`Internal server error: ${err}`);
  }
};

The controller use util.js function(s) which implements all database operations:

const comments = require('../models/comments');

exports.getComments = (query) => {
      try {
        return comments.find(query).sort({ createdAt: -1 });
      } catch (err) {
        throw err;
      }
};

How do I create unit test using mocha & chai? Do I have to create fake mock using sinon etc?

If I want to write test on method that cannot be avoided without mocking of db I use mongodb-memory-server that acts as database and simulates mongodb behaviour.

const Comment = require('../models/comments');

const mockedComments = [ // modify example data depending on Your model
  {userId: "1", body: "Mocked comment of user 1", createdAt: Date.now() },
  {userId: "2", body: "Mocked comment of user 2", createdAt: Date.now() },
];

const getComments = require('../path/to/getComments');


const mongoose = require('mongoose');
const MongodbMemoryServer = require('mongodb-memory-server');

let mongoServer;
const opts = { useMongoClient: true }; 

before((done) => {
  mongoServer = new MongodbMemoryServer();

  mongoServer.getConnectionString()
    .then((mongoUri) => {
      return mongoose.connect(mongoUri, opts, (err) => {
        if (err) done(err);
      });
    })
    .then(() => {
     // preparing in memory database contents 
     Promise.all([
       Comment.create(mockedComments[0]),
       Comment.create(mockedComments[1])
     ]).then(() => done());
    });
});

after(() => {
  mongoose.disconnect();
  mongoServer.stop();
});

describe('getComments', () => {

  it('successfully returns comments', async () => {
    const result = await getComments({});
    expect(result.length).to.equal(mockedComments.length);
  });

  it('successfully returns comments of user', async () => {
    const result = await getComments({userId: 1});
    expect(result[0].userId).to.equal(1);
  });
});

Wait wait....

The question states that we are talking about "unit tests", so unit test are those ones where we assess the correct beahviour of the function/class/whatever WE have developed. Just this and anything else. Now, too bad I'm not a mongoDB dev, neither a mongo-memory-server contributor, so I don't really need to take into account these softwares in my tests. This is the reason test doubles (stubs/mocks/spies) were born and we, as good software engineers, should make a wise use of them

so, this is my unit test:

 const {expect} = require("chai") const sinon = require("sinon") const uut = require("./users.service") const UserModel = require("./user.model") it("given correct userId should retrieve users full name" , async () => { //given const fixture = { _id : "fakeuser", name: "Fake", surname: "User" } let stub = sinon.stub(UserModel , "findById").returns(fixture) //when let result = await uut.getUserFullnameById(fixture._id) //then expect(result).to.eq("Fake User") stub.restore() })

This test tell me that getUserFullnameById function behaves correctly,

 const User = require("./user.model") module.exports.getUserFullnameById = async function (userId) { let user = await User.findById(userId) return `${user.name} ${user.surname}` }

I isolate my logic from mongoose because I don't need to know if mongoose works and I'm able to connect to a underlying mongodb instance. So someone could point that my test passes even if there is no "findById" API in mongoose library, so that's why I rely on integration tests as well

 describe("integration test" , () => { const mongoose = require("mongoose") before(()=> { mongoose.connect("mongodb://localhost:27017/db" , { useNewUrlParser: true , useUnifiedTopology: true}) mongoose.Promise = global.Promise }) beforeEach(async ()=> { await mongoose.connection.dropDatabase() }) it("given correct userId should retrieve users full name" , async () => { let fixture = new UserModel({name : "Fake" , surname : "User"}) await fixture.save() let result = await uut.getUserFullnameById(fixture._id) expect(result).to.eq("Fake User") }) })

the integration test does the same thing as before, but does it whithin a context. if i mess around with my method under test, I could break the integration test and i will know that I am misusing mongoose or mongodb connection, or I can break both, so I know that I am likely just disrespecting my business rules. Another advantage: integration tests are slow and brittle, but I can provide quality code even providing less of them and adding more isolated unit tests

Now you have two more cases: first name not present and last name not present: how do you test and extend your code?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM