Efemorav
Efemorav

Reputation: 143

Jest testing mongoose model instantiation

I'm trying to test a REST API built with express and mongoose, I'm using jest and supertest for the http calls; also I'm relatively new to testing with javascript.

When testing a creation url I wan't to make sure the instantiation is called using just the req.body object but I'm not sure how to do it, after reading a lot about differences between mock objects and stubs and some of the Jest documentation my last try looks like this:

test('Should instantiate the model using req.body', done => {

  const postMock = jest.fn();

  const testPost = {
    name: 'Test post',
    content: 'Hello'
  };

  postMock.bind(Post); // <- Post is my model

  // I mock the save function so it doesn't use the db at all
  Post.prototype.save = jest.fn(cb => cb(null, testPost));

  // Supertest call
  request(app).post('/posts/')
  .send(testPost)
  .then(() => {
    expect(postMock.mock.calls[0][0]).toEqual(testPost);
    done();
  })
  .catch(err => {throw err});

});

Also I would like to know how to manually fail the test on the promise rejection, so it doesn't throws the Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

Upvotes: 2

Views: 2103

Answers (1)

Cisco
Cisco

Reputation: 22952

As it stands, you're performing more of a integration test rather than isolating the route handler function itself and testing just that.

First I would break away the handler for /posts/ to its own file (assuming you haven't done this already):

controllers/post-controller.js

const Post = require('./path/to/models/post')

exports.store = async (req, res) => {
  const post = await new Post(req.body).save()
  res.json({ data: post }
}

Next simply use the handler wherever you defined your routes:

const express = require('express')
const app = express()
const postController = require('./path/to/controllers/post-controller')

app.post('/posts', postController.store)

With this abstraction we can now isolate our postController.store and test that it works with req.body. Now since we need to mock mongoose to avoid hitting an actual database, you can create a mocked Post like so (using the code you already have):

path/to/models/__mocks__/post.js

const post = require('../post')

const mockedPost = jest.fn()
mockedPost.bind(Post)

const testPost = {
  name: 'Test post',
  content: 'Hello'
}


Post.prototype.save = jest.fn(cb => {
  if (typeof cb === 'function') {
    if (process.env.FORCE_FAIL === 'true') {
      process.nextTick(cb(new Error(), null))
    } else {
      process.nextTick(cb(null, testPost))
    }
  } else {
    return new Promise((resolve, reject) => {
      if (process.env.FORCE_FAIL === 'true') {
        reject(new Error())
      } else {
        resolve(testPost)
      }
    })
  }
})

module.exports = mockedPost

Notice the check for process.env.FORCE_FAIL if for whatever reason you wanted to fail it.

Now we're ready to test that using the req.body works:

post-controller.test.js

// Loads anything contained in `models/__mocks__` folder
jest.mock('../location/to/models')

const postController = require('../location/to/controllers/post-controller')

describe('controllers.Post', () => {
  /**
   * Mocked Express Request object.
   */
  let req

  /**
   * Mocked Express Response object.
   */
  let res

  beforeEach(() => {
    req = {
      body: {}
    }
    res = {
      data: null,
      json(payload) {
        this.data = JSON.stringify(payload)
      }
    }
  })

  describe('.store()', () => {
    test('should create a new post', async () => {
      req.body = { ... }
      await postController(req, res)
      expect(res.data).toBeDefined()

      ...
    })

    test('fails creating a post', () => {
      process.env.FORCE_FAIL = true
      req.body = { ... }

      try {
        await postController.store(req, res)
      } catch (error) {
        expect(res.data).not.toBeDefined()

        ...
      }
    })

  })
})

This code is untested, but I hope it helps in your testing.

Upvotes: 7

Related Questions