felixmosh / knex-mock-client

A mock client for knex which allows you to write unit tests with DB interactions

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Invalid variable access: knex

ash-vd opened this issue · comments

Thank you for your work so far, this library looks like exactly what I need. However, when I try the example from the readme, I get an error:

The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables. Invalid variable access: knex

Is there some configuration I'm missing? I can fix that error by using

jest.mock('../../knex', () => {
    const knex = require('knex');
    const mockKnexClient = require('knex-mock-client');
    return knex({ client: mockKnexClient.MockClient });
});

but then the next error appears:
TypeError: Class extends value undefined is not a constructor or null

What type of file does this library expect ../common/db-setup from the example to be? I tried export const db = knex(config); and export default knex(config); but that doesn't seem to be it.

Thank you for opening this issue, I will fix the README example.

It depends on what your mocked module (in your case it is ../../knex) exports.

can you share it?

BTW, if your app gets its knex instance from "the outside" you can pass other instance (the one that will use knex-mock-client) in your tests.

This is my ../../knex/index.ts-file in full:

import config from '../knexfile.js';

/**
 * Global is used here to ensure the connection
 * is cached across hot-reloads in development
 *
 * see https://github.com/vercel/next.js/discussions/12229#discussioncomment-83372
 */
// @ts-expect-error TS2339: Property 'mysql' does not exist on type 'Global & typeof globalThis'.
let cached = global.mysql;
// @ts-expect-error TS2339: Property 'mysql' does not exist on type 'Global & typeof globalThis'.
if (!cached) cached = global.mysql = {};

export function getKnex() {
    if (!cached.instance) cached.instance = knex(config);
    return cached.instance;
}

export default knex(config);

I added the default export and tried using that export in both my code and tests, but it doesn't work. Thanks for your effort!

I've just tested the README example,

import knex from 'knex';
import { getTracker } from '../dist';
import { MockClient } from '../dist';
import { db } from './db';

jest.mock('./db', () => {
  return { db: knex({ client: MockClient }) };
});

describe('jest mock', () => {
  it('should work', async () => {
    const tracker = getTracker();
    tracker.on.select('table_name').responseOnce([]);
    const response = await db.select('table_name');

    expect(response).toEqual([]);
  });
});

When ./db file is

// ./db.ts

import knex from 'knex';

export const db = knex({
  client: 'mysql',
});

And I don't get any of the errors you've mentioned.

If your production code uses getKnex function, your jest.mock should mimic it.

it should look like:

jest.mock('../../knex/index', () => {
  return {
    getKnex: () => {
      return knex({ client: MockClient });
    },
  };
});
  • It maybe an issue with the mocking path, if your app imports ../../knex (as nodejs allows to omit the index) you should specify the same way.

As per export default, jest docs specifies that you should mock it like this:

jest.mock('./esModule', () => ({
  __esModule: true, // <-- this property is important
  default: knex({ client: MockClient }),
}));

Yep, that's what I tried at first but that's when I get the Invalid variable access error. I changed it to:

jest.mock('../../knex/index.ts', () => {
    const knex = require('knex');
    return {
        getKnex: () => {
            return knex({ client: MockClient });
        },
    };
});

But that gives me the second error:

TypeError: Class extends value undefined is not a constructor or null
  at Object.<anonymous> (node_modules/knex-mock-client/dist/MockClient.js:9:41)

I'm really not sure what could cause this error. For the sake of completeness, my jest.config.js:

module.exports = {
    collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/*.d.ts', '!**/node_modules/**'],
    setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
    testPathIgnorePatterns: ['/node_modules/', '/.next/'],
    preset: 'ts-jest',
    testEnvironment: 'node',
};

package.json:

"ts-jest": "^26.5.6",
"jest": "^26.6.3",

What is your knex version & node version?

I've tried version 0.95.4 to 0.95.6. Node is version 14.16.0.

Weird, I'm using the same config.

Do you use TypeScript? if so, do you have "esModuleInterop": true, in your tsconfig.json?

Yes I have. Here is my full tsconfig.json:

{
    "ts-node": {
        "compilerOptions": {
            "module": "commonjs"
        }
    },
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext", "ESNext.Intl", "ES2018.Intl"],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": false,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "preserve",
        "jsxImportSource": "@emotion/react",
        "typeRoots": ["types", "node_modules/@types"]
    },

    "include": ["**/*.ts", "**/*.tsx"],
    "exclude": ["node_modules"]
}

From your error, looks like there is some issue with the default import of knex.

Can you put a console log in ./node_modules/knex-mock-client/dist/MockClient.js.

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MockClient = void 0;
const knex_1 = __importDefault(require("knex"));
const Tracker_1 = require("./Tracker");

console.log(knex_1.default) // these 2
console.log(knex_1.default.Client)

class MockClient extends knex_1.default.Client {
...

or just prepare a small repo which will reproduce the bug

I've added the console.logs. knex itself is defined, however knex.Client is not.

 console.log
    [Function (anonymous)]

      at Object.<anonymous> (node_modules/knex-mock-client/dist/MockClient.js:11:9)

  console.log
    undefined

      at Object.<anonymous> (node_modules/knex-mock-client/dist/MockClient.js:12:9)

I'll setup a test repo to make things a bit easier to debug.

If I log knex_1.default(), as a function, I get the following result:

    {
      select: [Function: mockConstructor] {
        _isMockFunction: true,
        getMockImplementation: [Function (anonymous)],
        mock: [Getter/Setter],
        ...
      },
      from: [Function: mockConstructor] {
        _isMockFunction: true,
        getMockImplementation: [Function (anonymous)],
        mock: [Getter/Setter],
        ...
      },
      where: [Function: mockConstructor] {
        _isMockFunction: true,
        getMockImplementation: [Function (anonymous)],
        mock: [Getter/Setter],
        ...
      }
    }

Ha, looks you have another global mock for knex :]

Maybe inside __mocks__

Did you found the code that mocks knex?

Sorry, yes I did. I feel kinda stupid now, but it works :). It doesn't work with the getKnex-function, if I try to mock that I can't use the tracker. This isn't a big deal since I only need that in development. Thank you for your help!

Glad to hear

if you have any feature requests / issues with the lib, I'll more than happy to help :]