150

I have a file that relies on an exported const variable. This variable is set to true but if ever needed can be set to false manually to prevent some behavior if downstream services request it.

I am not sure how to mock a const variable in Jest so that I can change its value for testing the true and false conditions.

Example:

//constants module
export const ENABLED = true;

//allowThrough module
import { ENABLED } from './constants';

export function allowThrough(data) {
  return (data && ENABLED === true)
}

// jest test
import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';

describe('allowThrough', () => {
  test('success', () => {
    expect(ENABLED).toBE(true);
    expect(allowThrough({value: 1})).toBe(true);
  });

  test('fail, ENABLED === false', () => {
    //how do I override the value of ENABLED here?

    expect(ENABLED).toBe(false) // won't work because enabled is a const
    expect(allowThrough({value: 1})).toBe(true); //fails because ENABLED is still true
  });
});
danronmoon
  • 3,814
  • 5
  • 34
  • 56
Mdd
  • 4,140
  • 12
  • 45
  • 70

15 Answers15

90

This example will work if you compile ES6 modules syntax into ES5, because in the end, all module exports belong to the same object, which can be modified.

import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';
import * as constants from './constants';

describe('allowThrough', () => {
    test('success', () => {
        constants.ENABLED = true;

        expect(ENABLED).toBe(true);
        expect(allowThrough({ value: 1 })).toBe(true);
    });

    test('fail, ENABLED === false', () => {
        constants.ENABLED = false;

        expect(ENABLED).toBe(false);
        expect(allowThrough({ value: 1 })).toBe(false);
    });
});

Alternatively, you can switch to raw commonjs require function, and do it like this with the help of jest.mock(...):

const mockTrue = { ENABLED: true };
const mockFalse = { ENABLED: false };

describe('allowThrough', () => {
    beforeEach(() => {
        jest.resetModules();
    });

    test('success', () => {
        jest.mock('./constants', () => mockTrue)
        const { ENABLED } = require('./constants');
        const { allowThrough } = require('./allowThrough');

        expect(ENABLED).toBe(true);
        expect(allowThrough({ value: 1 })).toBe(true);
    });

    test('fail, ENABLED === false', () => {
        jest.mock('./constants', () => mockFalse)
        const { ENABLED } = require('./constants');
        const { allowThrough } = require('./allowThrough');

        expect(ENABLED).toBe(false);
        expect(allowThrough({ value: 1 })).toBe(false);
    });
});
eur00t
  • 1,400
  • 10
  • 7
  • Is there a way to do both imports **import { ENABLED } from './constants'; import * as constants from './constants';** in one line? I tried _import * as constants, { ENABLED } from './constants';_ but throws syntax error – Doug Nov 03 '17 at 12:20
  • 4
    I'm getting this message `The module factory of jest.mock() is not allowed to reference any out-of-scope variables.`. Did this happen to you? – alayor Nov 23 '17 at 22:21
  • 8
    The first example is mutating a const which is not allowed? It works due to the * as constants which wrap everything in a object but if you use flowtype this is an error. – Jon Miles May 16 '18 at 11:46
  • second snippet works properly only if you use `require(your_module)`. `import {...} from 'your_module'` doesn't work at tests. – LazyCat01 Feb 21 '19 at 13:48
  • 28
    First snippet got me `TS2540: Cannot assign to '' because it is a read-only property.` error – Duc Tran Sep 26 '20 at 22:22
  • 2
    How do you so easily re-assign the constant variables? – freethinker Sep 02 '21 at 13:25
  • For typescript, the first snippet works as expected but you need to disable the compiler checks for lines with TS2540 errors (add `// @ts-ignore` comment above these lines). Also, I like to reset the constants in the `afterAll()`. – Luka Kralj Feb 26 '23 at 13:03
73

Unfortunately none of the posted solutions worked for me or to be more precise some did work but threw linting, TypeScript or compilation errors, so I will post my solution that both works for me and is compliant with current coding standards:

// constants.ts
// configuration file with defined constant(s)
export const someConstantValue = true;
// module.ts
// this module uses the defined constants
import { someConstantValue } from './constants';

export const someCheck = () => someConstantValue ? 'true' : 'false';
// module.test.ts
// this is the test file for module.ts
import { someCheck } from './module';

// Jest specifies that the variable must start with `mock`
const mockSomeConstantValueGetter = jest.fn();
jest.mock('./constants', () => ({
  get someConstantValue() {
    return mockSomeConstantValueGetter();
  },
}));

describe('someCheck', () => {
  it('returns "true" if someConstantValue is true', () => {
    mockSomeConstantValueGetter.mockReturnValue(true);
    expect(someCheck()).toEqual('true');
  });

  it('returns "false" if someConstantValue is false', () => {
    mockSomeConstantValueGetter.mockReturnValue(false);
    expect(someCheck()).toEqual('false');
  });
});
Dimitri L.
  • 4,499
  • 1
  • 15
  • 19
  • 1
    This gives me an error: "The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables." – Ev Haus Jun 21 '21 at 17:30
  • 2
    @EvHaus the variable name must start with `mock` as in the example `const mockSomeConstantValueGetter = jest.fn();` – Dimitri L. Jun 22 '21 at 16:47
  • 4
    This gives me `ReferenceError: Cannot access 'mockSomeConstantValueGetter' before initialization`. I am using a CRA. Is there an extra config needed to disable the hoisting? – Ivan Wang Aug 03 '21 at 07:28
  • @IvanWang this means that you have not defined a variable `mockSomeConstantValueGetter` – Dimitri L. Aug 03 '21 at 12:02
  • 1
    Love this! And also learned about the "get" keyword today! Thank you so much – Thales Kenne Apr 21 '22 at 21:18
  • This worked for me only if `mockSomeConstantValueGetter` and `jest.mock(...)` are defined outside of `describe(...)`. – Faber Sep 04 '22 at 16:52
  • Maybe an unrelated question, but is it possible to only partially `jest.mock()` an import and otherwise leave parts untouched? – MattTreichel Feb 03 '23 at 21:33
  • If I combine this with the instructions for partially mocking an import https://jestjs.io/docs/mock-functions#mocking-partials, I get `ReferenceError: Cannot access 'mockSomeConstantValueGetter' before initialization` – MattTreichel Feb 03 '23 at 22:15
  • @MattTreichel looks like `mockSomeConstantValueGetter` is not defined when you try to use it: `const mockSomeConstantValueGetter = jest.fn();` – Dimitri L. Feb 04 '23 at 22:07
  • Yes, this happens when I use your working implementation and then include an invocation of `jest.requireActual()` within `jest.mock()` - I assume something changes about the hoisting order and causes this error. I'm not sure if it can be worked around. – MattTreichel Feb 06 '23 at 01:34
  • @MattTreichel it's hard to tell where the problem is without seeing your code – Dimitri L. Feb 06 '23 at 11:49
  • It seems to me this `const mockSomeConstantValueGetter = jest.fn(); jest.mock('./constants', () => { return { ...jest.requireActual('./constants'), get someConstantValue() { return mockSomeConstantValueGetter(); }}});` blows up the existing solution, the inclusion of `jest.requireActual` changes the hoisting. – MattTreichel Feb 06 '23 at 16:28
  • @IvanWang you can reference to my answer which use inline require to solve your issue. – ZenG Mar 17 '23 at 05:32
54

There is another way to do it in ES6+ and jest 22.1.0+ thanks to getters and spyOn.

By default, you cannot spy on primitive types like boolean or number. You can though replace an imported file with your own mock. A getter method still acts like a primitive member but allows us to spy on it. Having a spy on our target member you can basically do with it whatever you want, just like with a jest.fn() mock.

Below an example

// foo.js
export const foo = true; // could be expression as well
// subject.js
import { foo } from './foo'

export default () => foo
// subject.spec.js
import subject from './subject'

jest.mock('./foo', () => ({
  get foo () {
    return true // set some default value
  }
}))

describe('subject', () => {
  const mySpy = jest.spyOn(subject.default, 'foo', 'get')

  it('foo returns true', () => {
    expect(subject.foo).toBe(true)
  })

  it('foo returns false', () => {
    mySpy.mockReturnValueOnce(false)
    expect(subject.foo).toBe(false)
  })
})

Read more in the docs.

Luke
  • 2,350
  • 6
  • 26
  • 41
  • https://www.npmjs.com/package/jest-mock-primitive uses getters to accomplish a similar thing /self-promotion. – reergymerej Jun 10 '19 at 17:39
  • 1
    Seems like it should work, but I couldn't get this working. Using `doMock` worked for me - see https://jestjs.io/docs/en/jest-object.html#jestdomockmodulename-factory-options – David Calhoun Sep 29 '20 at 21:42
22

Since we can't override/mock the value directly. we can use the below hack

// foo.js
export const foo = true; // could be expression as well

// spec file
import * as constants from './foo'

Object.defineProperty(constant, 'foo', {value: 1})

For functions:

Object.defineProperty(store, 'doOneThing', {value: jest.fn()})
Suresh Prajapati
  • 3,991
  • 5
  • 26
  • 38
21

Thanks to @Luke I was able to expand on his answer for my needs. I had the requirements of:

  • Only mocking certain values in the file - not all
  • Running the mock only inside a single test.

Turns out that doMock() is like mock() but doesn't get hoisted. In addition requireActual() can be used to grab original data.

My config.js file - I need to mock only part of it

export const SOMETHING = 'blah'
export const OTHER = 'meh'

My test file

// import { someFunc } from  'some/file' // This won't work with doMock - see below
describe('My test', () => {

  test('someFunc() does stuff', async () => {

    // Here I mock the config file which gets imported somewhere deep in my code
    jest.doMock('config.js', () => {

      // Grab original
      const originalModule = jest.requireActual('config')

      // Return original but override some values
      return {
        __esModule: true, // Depends on your setup
        ...originalModule,
        SOMETHING: 'boom!'
      }
    })

    // Because `doMock` doesn't get hoisted we need to import the function after
    const { someFunc } = await import(
      'some/file'
    )

    // Now someFunc will use the original config values but overridden with SOMETHING=boom!
    const res = await someFunc()
  })
})

Depending on other tests you may also need to use resetModules() somewhere such as beforeAll or afterAll.

Docs:

cyberwombat
  • 38,105
  • 35
  • 175
  • 251
  • 2
    Looks like other answers that reference this one gloss over the fact that with this answer, the mocks are scoped, and that's what the OP was ultimately after. – shellscape Jun 14 '21 at 13:46
9

For me the simplest solution was to redefine the imported object property, as decribed here:

https://flutterq.com/how-to-mock-an-exported-const-in-jest/

// bar.js
export const foo = true; // could be expression as well

// spec file
import * as constants from './bar'

Object.defineProperty(constant, 'foo', {value: 1, writable: true})

there is also an alternate solution I noticed form another post. Basically to mock the whole imported module as

// spec file
jest.mock('./bar', () => ({
  ...jest.requireActual('./bar'),
  foo: 3
}));
Alberto S.
  • 1,805
  • 23
  • 39
  • I tried this way and these ones I have created here and didn't work: https://stackoverflow.com/questions/75137433/jest-typeerror-0-windowvars-myfunction-is-not-a-function I have to be doing something wrong – Dani Jan 17 '23 at 16:54
  • @Dani can u show a code example ? thanks! – Alberto S. Jan 17 '23 at 17:24
6

Facing the same issue, I found this blog post very useful, and much simpler than @cyberwombat use case :

https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  namedExport: jest.fn(),
}));

import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function
6

The most common scenario I needed was to mock a constant used by a class (in my case, a React component but it could be any ES6 class really).

@Luke's answer worked great for this, it just took a minute to wrap my head around it so I thought I'd rephrase it into a more explicit example.

The key is that your constants need to be in a separate file that you import, so that this import itself can be stubbed/mocked by jest.

The following worked perfectly for me.

First, define your constants:

// src/my-component/constants.js

const MY_CONSTANT = 100;

export { MY_CONSTANT };

Next, we have the class that actually uses the constants:

// src/my-component/index.jsx

import { MY_CONSTANT } from './constants';

// This could be any class (e.g. a React component)
class MyComponent {
  constructor() {
    // Use the constant inside this class
    this.secret = MY_CONSTANT;
    console.log(`Current value is ${this.secret}`);
  }
}

export default MyComponent

Lastly, we have the tests. There's 2 use cases we want to handle here:

  1. Mock the generate value of MY_CONSTANT for all tests inside this file
  2. Allow the ability for a specific test to further override the value of MY_CONSTANT for that single test

The first part is acheived by using jest.mock at the top of your test file.

The second is acheived by using jest.spyOn to further spy on the exported list of constants. It's almost like a mock on top of a mock.

// test/components/my-component/index.js

import MyComponent from 'src/my-component';
import allConstants from 'src/my-component/constants';

jest.mock('src/my-component/constants', () => ({
  get MY_CONSTANT () {
    return 30;
  }
}));

it('mocks the value of MY_CONSTANT', () => {
  // Initialize the component, or in the case of React, render the component
  new MyComponent();

  // The above should cause the `console.log` line to print out the 
  // new mocked value of 30
});

it('mocks the value of MY_CONSTANT for this test,', () => {
  // Set up the spy. You can then use any jest mocking method
  // (e.g. `mockReturnValue()`) on it
  const mySpy = jest.spyOn(allConstants, 'MY_CONSTANT', 'get')
  mySpy.mockReturnValue(15);

  new MyComponent();

  // The above should cause the `console.log` line to print out the 
  // new mocked value of 15
});
Striped
  • 2,544
  • 3
  • 25
  • 31
user2490003
  • 10,706
  • 17
  • 79
  • 155
5

One of the way for mock variables is the follow solution:

For example exists file ./constants.js with constants:

export const CONSTATN_1 = 'value 1';
export const CONSTATN_2 = 'value 2';

There is also a file of tests ./file-with-tests.spec.js in which you need to do mock variables. If you need to mock several variables you need to use jest.requireActual to use the real values of the remaining variables.

jest.mock('./constants', () => ({
  ...jest.requireActual('./constants'),
  CONSTATN_1: 'mock value 1',
}));

If you need to mock all variables using jest.requireActual is optional.

jest.mock('./constants', () => ({
  CONSTATN_1: 'mock value 1',
  CONSTATN_2: 'mock value 2'
}));
an_parubets
  • 589
  • 7
  • 15
4

Instead of Jest and having trouble with hoisting etc. you can also just redefine your property using "Object.defineProperty"

It can easily be redefined for each test case.

This is a pseudo code example based on some files I have:

From localization file:

export const locale = 'en-US';

In another file we are using the locale:

import { locale } from 'src/common/localization';
import { format } from 'someDateLibrary';

// 'MMM' will be formatted based on locale
const dateFormat = 'dd-MMM-yyyy';

export const formatDate = (date: Number) => format(date, dateFormat, locale)

How to mock in a test file

import * as Localization from 'src/common/localization';
import { formatDate } from 'src/utils/dateUtils';

describe('format date', () => {
        test('should be in Danish format', () => {
            Object.defineProperty(Localization, 'locale', {
                value: 'da-DK'
            });
            expect(formatDate(1589500800000)).toEqual('15-maj-2020');
        });
        test('should be in US format', () => {
            Object.defineProperty(Localization, 'locale', {
                value: 'en-US'
            });
            expect(formatDate(1589500800000)).toEqual('15-May-2020');
        });
});
Rasmus Knap
  • 258
  • 2
  • 13
  • I tried many different things in this thread and this is the only one that truly worked for me but theres no way to reset the values back to there original state easily – Marcellino Ornelas Aug 19 '23 at 21:47
2

in typescript, you can not overwrite constant value but; you can overwrite the getter function for it.

const mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter = jest.fn();
jest.mock('lib/constants', () => ({
  ...jest.requireActual('lib/constants'),
  get NEXT_PUBLIC_ENABLE_HCAPTCHA() {
    return mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter();
  },
}));

and in the test use as

      beforeEach(() => {
        mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter.mockReturnValue('true');
      });
Necmttn
  • 1,137
  • 1
  • 9
  • 17
2

Thank you all for the answers.

In my case this was a lot simpler than all the suggestions here

// foo.ts
export const foo = { bar: "baz" };
// use-foo.ts
// this is just here for the example to have a function that consumes foo
import { foo } from "./foo";

export const getFoo = () => foo;
// foo.spec.ts
import "jest";
import { foo } from "./foo";
import { getFoo } from "./use-foo";

test("foo.bar should be 'other value'", () => {
    const mockedFoo = foo as jest.Mocked<foo>;
    mockedFoo.bar = "other value";

    const { bar } = getFoo();
    expect(bar).toBe("other value"); // success
    expect(bar).toBe("baz"); // fail
};

Hope this helps someone.

Rik
  • 3,647
  • 2
  • 25
  • 34
0

../../../common/constant/file (constants file path)

export const Init = {
    name: "",
    basePath: "",
    description: "",
    thumbnail: "",
    createdAt: "",
    endDate: "",
    earnings: 0,
    isRecurring: false,
    status: 0,
  };

jest file

 jest.mock('../../../common/constant/file',()=>({
      get Init(){
        return {isRecurring: true}
      }
    }))

it('showActionbutton testing',()=>{
  const {result} = renderHook(() => useUnsubscribe())
  expect(result.current.showActionButton).toBe(true)
})

index file

import {Init} from ../../../common/constant/file

const useUsubscribe(){
  const showActionButton = Init.isRecurring
   return showActionButton
}
0

In order to solve the ReferenceError: Cannot access 'mockSomeConstantValueGetter' before initialization issue. We need to figure out a way to let jest.fn() happens before jest.mock(). My solution is to use inline require, it solve the problem perfectly.

This is where you define the const

// constants.ts
export const someConstantValue = true;

This is where you use the const

// module.ts
import { someConstantValue } from './constants';

export const someCheck = () => someConstantValue ? 'true' : 'false';

This is where you create mock function for the const

// mock.ts
export const mockSomeConstantValueGetter = jest.fn()

This is where mock const value and test module.ts

// module.test.ts
import { someCheck } from './module';

jest.mock('./constants', () => ({
  get someConstantValue() {
    const { mockSomeConstantValueGetter } = require("./mock")
    return mockSomeConstantValueGetter();
  },
}));

describe('someCheck', () => {
  it('returns "true" if someConstantValue is true', () => {
    const { mockSomeConstantValueGetter } = require("./mock")
    mockSomeConstantValueGetter.mockReturnValue(true);
    expect(someCheck()).toEqual('true');
  });

  it('returns "false" if someConstantValue is false', () => {
    const { mockSomeConstantValueGetter } = require("./mock")
    mockSomeConstantValueGetter.mockReturnValue(false);
    expect(someCheck()).toEqual('false');
  });
});

Thanks Dimitri L. for post the original answer!

ZenG
  • 139
  • 1
  • 3
-1

I solved this by initializing constants from ContstantsFile.js in reducers. And placed it in redux store. As jest.mock was not able to mock the contstantsFile.js

constantsFile.js
-----------------
const MY_CONSTANTS = {
MY_CONSTANT1: "TEST",
MY_CONSTANT2: "BEST",
};
export defualt MY_CONSTANTS;

reducers/index.js
-----------------
import MY_CONST from "./constantsFile";

const initialState = {
...MY_CONST
}
export const AbcReducer = (state = initialState, action) => {.....}

ABC.jsx
------------
import { useSelector } from 'react-redux';
const ABC = () => {
const const1 = useSelector(state) => state. AbcReducer. MY_CONSTANT1:
const const2 = useSelector(state) => state. AbcReducer. MY_CONSTANT2:
.......

Now we can easily mock the store in test.jsx and provide the values to constant that we want.

Abc.text.jsx
-------------
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';

describe('Abc mock constants in jest', () => {
const mockStore = configureMockStore([thunk]);
let store = mockStore({
   AbcReducer: {
      MY_CONSTANT1 ="MOCKTEST",
      MY_CONSTANT2 = "MOCKBEST",
   }
});

test('your test here', () => { .....

Now when the test runs it will always pick the constant value form mock store.

Cyruses Cyrus
  • 109
  • 1
  • 6