How to mock an exported const in jest

后端 未结 8 1704
借酒劲吻你
借酒劲吻你 2020-12-23 18:35

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

相关标签:
8条回答
  • 2020-12-23 19:15

    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.

    0 讨论(0)
  • 2020-12-23 19:19

    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);
        });
    });
    
    0 讨论(0)
  • 2020-12-23 19:21

    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';
    
    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');
      });
    });
    
    0 讨论(0)
  • 2020-12-23 19:25

    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:

    • doMock
    • requireActual
    • resetModules
    0 讨论(0)
  • 2020-12-23 19:32

    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');
            });
    });
    
    0 讨论(0)
  • 2020-12-23 19:35

    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 30
    });
    
    0 讨论(0)
提交回复
热议问题