How to mock a constant object in Jest

Asked

Viewed 86 times

-1

Hello I have got a constant file set like this in an React project:

src/constants/env.ts

export default {
   PROD: ['prod', 'production'].includes((process.env.NODE_ENV || '').toLowerCase())
}

This file is used to set some settings as in the example below:

src/utils/storage/Storage.ts

import ENV from 'src/constants/env'

class Storage {
  async get<T = any>(key: string): Promise<T | undefined> {
    return await new Promise((resolve) => {
      try {
        let value = localStorage.getItem(key) as string
        if (value) {
          if (ENV.PROD) value = btoa(value)
          resolve(JSON.parse(value) as any as T)
        }
      } catch (error) { }
      resolve(undefined)
    })
  }

  async set(key: any, value: any): Promise<void> {
    return await new Promise((resolve) => {
      try {
        value = JSON.stringify(value)
        if (ENV.PROD) value = atob(value)
        localStorage.setItem(key, value)
      } catch (error) {
        console.log(error.message)
      }
      resolve()
    })
  }
}

export default new Storage()

And I’m trying to mock the value of PROD as true for the Storage class to read ENV as true and then save the data to Base64.

src/utils/storage/Storage.test.ts

import Storage from './Storage'
import ENV from 'src/constants/env'

describe('Storage', () => {
  const key = 'test'
  const value = { test: 'test' }

  const { PROD } = ENV

  beforeEach(() => {
    localStorage.clear()
    ENV.PROD = false
  })

  afterAll(() => ENV.PROD = PROD)

  describe('set', () => {
    test('should set encoded base64 data if prod mode', async () => {
      ENV.PROD = true
      await Storage.set(key, value)
      expect(localStorage.getItem(key)).toEqual(atob(JSON.stringify(value)))
    })
  })
})

But I’m not getting the Storage class to read the PROD value as true. What is required to mock this constants file? Can anyone help me?

  • 1

    Maybe I can help: Manual Mocks.

  • I saw no place in the documentation that allowed me to mock this constant file... some other idea

1 answer

0

As suggested in the comments, you can choose to use the manual mocks.

Using manual mocks

Basically, you could create, within the resource directory that you’re importing, another directory called __mocks__, where you create a file of the same name as the one you are importing.

Let’s try to understand through an example:

.
├── babel.config.js
├── jest.config.js
├── package-lock.json
├── package.json
├── src
│   ├── __tests__
│   │   └── util.js
│   ├── constants
│   │   ├── __mocks__
│   │   └── env.js
│   ├── index.js
│   └── util.js
└── webpack.config.js

Where src/index.js corresponds to:

import { getAppEnvironment } from './util';

(function() {
  console.log(getAppEnvironment());
})();

src/util.js corresponds to:

import ENV from './constants/env';

export function getAppEnvironment() {
  return ENV.PRODUCTION ? 'Production' : 'Development';
}

And src/constants/env.js:

export default {
  PRODUCTION: ["prod", "production"].includes((process.env.NODE_ENV || '').toLowerCase())
}

As in your case.


So when will I take the test in src/__tests__/util.js, i do:

jest.mock('../constants/env');

const { getAppEnvironment } = require("../util");

describe("Testing the consistency of the environment teller", () => {
  it("should return 'Production' when it's on production", () => {
    require('../constants/env').__setEnvironmentData('PRODUCTION', true);

    let environment = getAppEnvironment();

    expect(environment).toBe('Production');
  });

  it("should return 'Development' when it's on development", () => {
    require('../constants/env').__setEnvironmentData('PRODUCTION', false);

    let environment = getAppEnvironment();

    expect(environment).toBe('Development');
  })
});

Note that before importing the function getAppEnvironment, and especially if I was using a import, I use the method jest.mock, so that the module constants/env to be imported, be the module mockery (contained in constants/__mocks__), and not the original module.

But even if you don’t do the mocking before import, according to the documentation, Jest himself will take care of it.


Then you can think: Wait, what a strange thing that is?

    require('../constants/env').__setEnvironmentData('PRODUCTION', true);

That’s a function, present in the module mockery, that we will be using to change the value of the "constant" PRODUCTION for whatever we want. And here’s the module code mockery:

const data = { PRODUCTION: false };

export function __setEnvironmentData(key, value) {
  data[key] = value;
}

export default data;

Where I simply assign an object to data, that will correspond to the object that would be returned by the original module, and define the function __setEnvironmentData, which, as stated before, will be responsible for modifying the value of the "constant".

With all this, we can realize that the responsible for importing the module constants/env (of course, during the tests) it will be Jest himself, because he will be responsible for exchanging the original module for the mock, which will allow the exchange of the value of the "constant".

Running the tests

Let’s run the tests:

device:481942 gustavosampaio$ npm test

> [email protected] test /path/to/SO/481942
> jest

 PASS  src/__tests__/util.js
  Testing the consistency of the environment teller
    ✓ should return 'Production' when it's on production (1 ms)
    ✓ should return 'Development' when it's on development

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.009 s
Ran all test suites.
device:481942 gustavosampaio$ 

We managed to do the mock module!

And if you want to attest that this is really it, try to comment on the jest.mock and then the function calls __setEnvironmentData. :)


recalling that in my case it was only possible to use the syntax import of ES, through Webpack and Babel! ;)

Browser other questions tagged

You are not signed in. Login or sign up in order to post.