Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing functions that have database calls with Jest

I'm new to Jest and code testing in general. I would like to test a function that retrieves data from a mongodb database using mongoose. When I run the test all the calls to the db inside the function I'm testing return null. What's the recommended way to test functions that need to retrieve data from a database?

Let me know if you need code samples or more information.

Update:

Here's my code:

code.js

...
function buildText(listing, messageRule, reservation, isReview = false, isLastMinuteMessage = false) {
    return new Promise(async function(resolve) {
        const name = reservation.name;
        try {
            message = await Message.findOne({
                listingID: listing._id,
                messageRuleID: messageRule._id,
                reservationID: reservation._id,
                message: {$exists: true}
            });
        } catch (error) {
            console.error(error);
        }
        if (message) {
            text = message.message;
        } else {
            text = messageRule.message;
            if (isLastMinuteMessage) {
                text = messageRule.lastMinuteMessage;
            }
        }
        text = text.replace(/{{Guest Name}}/g, name);
        resolve(text);
    });
}
...

code.test.js

const Code = require("./code");
describe("funcs", () => {
    it("buildText", async () => {
        let listing = {
            _id: "1324",
            pricingEnabled: true,
            listingFound: true,
            hideListing: null
        };
        let messageRule = {
            _id: "3452345",
            minNights: 1,
            reviewEnabled: false,
            disableMessageAfterReview: false,
            message: 'test',
            lastMinuteMessage: 'test2'
        };
        let reservation = {
            _id: "63452345",
            hasLeftReview: true,
            hasIssues: false
        };
        let isReview = false;
        let isLastMinuteMessage = false;
        let response = await Code.buildText(listing, messageRule, reservation, isReview, isLastMinuteMessage);
        expect(response).toMatchSnapshot();
    });
});

The trouble I'm having is with the message = await Message.findOne({ line in my code file. It always returns null but I need to be able to tell it to return true or false. How can I do that?

like image 418
Dev01 Avatar asked Sep 06 '18 19:09

Dev01


Video Answer


3 Answers

Here is a simple working example based on the code provided:


code.js

const { Message } = require('./message');

function buildText(listing, messageRule, reservation, isReview = false, isLastMinuteMessage = false) {
  return new Promise(async function (resolve) {
    let message, text;
    const name = reservation.name;
    try {
      message = await Message.findOne({
        listingID: listing._id,
        messageRuleID: messageRule._id,
        reservationID: reservation._id,
        message: { $exists: true }
      });
    } catch (error) {
      console.error(error);
    }
    if (message) {
      text = message.message;
    } else {
      text = messageRule.message;
      if (isLastMinuteMessage) {
        text = messageRule.lastMinuteMessage;
      }
    }
    text = text.replace(/{{Guest Name}}/g, name);
    resolve(text);
  });
}

module.exports = {
  buildText
}

code.test.js

const Code = require('./code');
const { Message } = require('./message');

describe("funcs", () => {
  it("buildText", async () => {
    const mock = jest.spyOn(Message, 'findOne');  // spy on Message.findOne()
    mock.mockImplementation(() => Promise.resolve({
      message: 'the message'
    }));  // replace the implementation

    let listing = {
      _id: "1324",
      pricingEnabled: true,
      listingFound: true,
      hideListing: null
    };
    let messageRule = {
      _id: "3452345",
      minNights: 1,
      reviewEnabled: false,
      disableMessageAfterReview: false,
      message: 'test',
      lastMinuteMessage: 'test2'
    };
    let reservation = {
      _id: "63452345",
      hasLeftReview: true,
      hasIssues: false
    };
    let isReview = false;
    let isLastMinuteMessage = false;
    let response = await Code.buildText(listing, messageRule, reservation, isReview, isLastMinuteMessage);
    expect(response).toMatchSnapshot();

    mock.mockRestore();  // restore Message.findOne()
  });
});

message.js (dummy model for the example)

const mongoose = require('mongoose');

exports.Message = mongoose.model('Message', new mongoose.Schema({ }));
like image 100
Brian Adams Avatar answered Oct 19 '22 19:10

Brian Adams


I'm not 100% sure about buildText (looks good to me though I never used new Promise(async ...)) - so I focus on the test: you are doing the essentials right as far as I can see. only thing is that you did not

// jest.mock('mongoose'); doesn't work, unfortunately
const mReal = require('mongoose');
const mMock = mReal; 

then add a beforeAll like

    beforeAll(() => { // not 110% clean, but I'm not alone: https://github.com/facebook/jest/issues/936#issuecomment-214556122
        mMock.model = jest.fn(); // manually overwrite one fct with a mock
    });

At the very first line of code.test.js; plus: you need to train the mock then, i.e. at the beginning if it insert

    it("...", () => {
        const findOneMock = jest.fn();
        findOneMock.mockReturnValue(Promise.resolve({message: "hey"}));
        mMock.model.mockReturnValue({findOne: findOneMock});
        // prep done, test something

I used some simple test code, I think your code will do an equivalent:

        let res = await mMock.model('whatever').findOne('whatever');
        expect(res.message).toBe("hey");
    });

Hope I got all hidden specifics (like how you come about Message) right - but per, you should now actually resolve to something.

like image 41
Sebastian Rothbucher Avatar answered Oct 19 '22 19:10

Sebastian Rothbucher


if you are using jest, then the best approach is to define a file called setup.js which runs before any of your test files get run, then in this file, you should import your mongoose models and then make a connection to your test database. (this is because your test files are running separately from your server files and do not know anything about your mongoose models or your database connection, so by defining this file and making connections, you are defining mongoose for your test environment and can use it as you want without getting null)

you need to go through three steps:

  1. first in your package.json in order to tell jest to run contents of a file before any other test file gets run, you need to add this block before your scripts block:

    "jest": {
      "setupTestFrameworkScriptFile": "./tests/setup.js"
    }
    
  2. then you need to require in your models from mongoose and the mongoose library in your setup.js file and then make a connection to your databse:

    require('../models/yourModel'); //require in any models that you need to use in your tests
    const mongoose = require('mongoose');
    
    mongoose.connect(mongoURI,{useMongoClient: true});
    mongoose.connection.once('open',()=>{}).on('error',(err)=> 
    {console.log(err)});
    
  3. then you can use your model in your test files to run or test any queries from mongoose, for Example, if you want to create a record of a model called Model in your test file:

    const mongoose = require('mongoose');
    const Model = mongoose.model('Model');
    const newRecord = await new Model({}).save();
    //test newRecord 
    
like image 1
Mehrnaz.sa Avatar answered Oct 19 '22 19:10

Mehrnaz.sa