Objection.js: How to test with jest ?

Created on 18 Mar 2020  路  15Comments  路  Source: Vincit/objection.js

I have model method for method of controller.
How to test it with jest ?
How to create mock of Objection ?

const { Errors } = require('../../db/models/Errors');

module.exports = async function get(page = 0, perPage = 5, errorsFilter = {}) {
  try {
    const query = Errors
      .query()
      .orderBy('id', 'DESC')
      .page(page, perPage);

    // filter

    if (Object.keys(errorsFilter).length) {
      for (let field in errorsFilter) {
        if ( errorsFilter.hasOwnProperty(field) ) {
          if (field === 'dateStart') {
            query.where('date', '>=', `${ errorsFilter[field] }`);
          } else if (field === 'dateEnd') {
            query.where('date', '<=', `${ errorsFilter[field] }`);
          } else {
            query.where(field, 'like', `%${ errorsFilter[field] }%`);
          }
        }
      }
    }

    const { results, total } = await query;

    return {
      items:       results,
      itemsOnPage: Number(perPage),
      currentPage: Number(page),
      totalPage:   Math.ceil(total/perPage),
      totalItems:  Number(total)
    }
  } catch (error) {
    // log error -----------------------------------------------------------
    console.log({Error: error});
    // ---------------------------------------------------------------------
  }
};

My test file:

require('dotenv').config();

const express       = require('express');
const request       = require('supertest');
const bodyParser    = require('body-parser');
const { Objection } = require('objection');
const { QueryBuilder } = require('objection');
const { Errors }    = require('../../../../server/db/models/Errors');

const errors      = require('../../../../server/controllers/errors');
const { i18next, i18nextMiddleware } = require('../../../i18n-server');

const builder = QueryBuilder.forClass(Errors);

console.log(builder.resolve);

jest.spyOn(Errors, 'query').mockImplementation(builder.resolve({ type: 'test' }));

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.use(i18nextMiddleware);

app.get(
  '/',
  errors.getErrors
);

describe('get errors', () => {
  it('success', done => {
    i18next.on('initialized', () => {
      request(app)
        .get('/')
        .query({page: 1, perPage: 5})
        .end((err, res) => {
          if (err) return done(err);

          expect(res.status).toBe(200);

          done();
        });
    })
  });
});

I have error:
TypeError: specificMockImpl.apply is not a function

Most helpful comment

I'm not sure if you should really be mocking out the database. I would consider any test that runs against Objection models to be an integration test, which should be backed by a real database.

If you really want to mock out the response, you could look at the resolve and reject Query Builder methods, although I'm unsure whether you can skip a database connection if using these.

As an aside, you might have more luck posting this sort of question on the gitter channel.

All 15 comments

Change to

jest.spyOn(Errors, 'query').mockReturnValue(builder.resolve({ list: 'test' }));

I have error

Error: no database connection available for a query. You need to bind the model class or the query to a knex instance.

Add knex

const { knex } = require('../../../../server/db/connect');
const { QueryBuilder, Objection, Model } = require('objection');

Model.knex(knex);

Now still there is a database request. How to disable it?

The error occurs because Model.knex(knex); required function.
Without it, tests do not pass.
If I disconnect my database, then how do I simulate a database connection?

const { results, total } = await query;

results - my mock values
total - value from db query

How to apply the method ?

const modelUser = jest.spyOn(Users, 'query');

modelUser
  .mockReturnValue(
    builder.resolve([
      {
        "id": 91,
        "login": "test_user",
        "type": "manager",
        "edit": 0,
        "email": "[email protected]",
        "phone": "79098160405",
        "block": 1
      },
      {
        "id": 77,
        "login": "y475y74gwytgyuhbtwghrwhg874yt1",
        "type": "manager",
        "edit": 0,
        "email": "[email protected]",
        "phone": "76756655457",
        "block": 0
      }
    ])
  );

jest.spyOn(modelUser, 'page').mockReturnValue({ total: 100 });

Error

Cannot spy the page property because it is not a function; undefined given instead

I mock query

jest.spyOn(Errors, 'query').mockReturnValue(
  builder.resolve(
  [
    { id: 1, name: 'Error 1' },
    { id: 2, name: 'Error 2' }
  ])
);

But page function is executed.

Result:

    { items: [ { id: 1, name: 'Error 1' }, { id: 2, name: 'Error 2' } ],
      itemsOnPage: 5,
      currentPage: 1,
      totalPage: 24,
      totalItems: 117 }

Where in the page method does the array with the results come from?

It is working. But maybe a better way?

jest.spyOn(Errors, 'query')
  .mockReturnValue(
    builder.resolve(errorsMockDB)
  );

jest.spyOn(builder, 'page')
  .mockImplementation(
    (page, perPage) => {
      return builder.resolve({
        items: errorsMockDB.getErrors.splice(page * perPage, perPage),
        total: errorsMockDB.getErrors.length
      })
    }
  );

Why doesn't where work ?

It is working

require('dotenv').config();

const express                           = require('express');
const request                           = require('supertest');
const bodyParser                        = require('body-parser');
const { Model }                   = require('objection');

const { Users }                                       = require('../../../../server/db/models/Users');
const users                                               = require('../../../../server/controllers/users');
const checkFilter                     = require('../../../../server/middleware/checkFilter');
const { i18next, i18nextMiddleware }    = require('../../../i18n-server');
const { knex }                                              = require('../../../../server/db/connect');
const usersMockDB                                         = require('../../../mock_db/users');

Model.knex(knex);

const builder = Users.query(knex);

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.use(i18nextMiddleware);

app.get(
  '/',
  checkFilter(['type']),
  users.getUsers
);

jest.spyOn(Users, 'query')
  .mockReturnValue(
    builder.resolve(usersMockDB.getUsers)
  );

describe('get errors', () => {
  beforeAll(done => {
    i18next.on('initialized', () => {
      done()
    });
  });

  it('without filter', done => {
    request(app)
      .get('/')
      .query({page: 0, perPage: 5})
      .end((err, res) => {
        if (err) return done(err);

        expect(res.status).toBe(200);
        expect(
          Object.keys(res.body).sort()
        ).toEqual([
          'items',
          'itemsOnPage',
          'currentPage',
          'totalPage',
          'totalItems'
        ].sort());
        expect(res.body.items).toHaveLength(8);
        expect(res.body.itemsOnPage).toBe(5);
        expect(res.body.currentPage).toBe(0);
        expect(res.body.totalPage).toBe(Math.ceil(usersMockDB.getUsers.length/5));
        expect(res.body.totalItems).toBe(usersMockDB.getUsers.length);

        console.log(res.body);

        done();
      });
  });

  afterAll(done => {
    knex.destroy();

    done();
  })
});

But value of totalItems from server ...

I created this function, see if it helps

import { QueryBuilder } from 'objection'
export default function mockmodel(Model) {
  const query = QueryBuilder.forClass(Model)
  jest.spyOn(Model, 'query').mockReturnValue(query)
  return query
}
const body = { test : 1 }
const queryBuilderMock = mockModel(YourModel).resolve(body)
YourModel.query.insert({})
// if needed
const insertMock = jest.spyOn(queryBuilderMock, 'insert')
expect(insertMock).toHaveBeenCalledTimes(1)

I'm not sure if you should really be mocking out the database. I would consider any test that runs against Objection models to be an integration test, which should be backed by a real database.

If you really want to mock out the response, you could look at the resolve and reject Query Builder methods, although I'm unsure whether you can skip a database connection if using these.

As an aside, you might have more luck posting this sort of question on the gitter channel.

@fiznool said it all :+1:

Was this page helpful?
0 / 5 - 0 ratings