meerkat
meerkat

Reputation: 1122

Mocking constant with Jest not working (Multiple mocks in same test file)

The problem I am having is that the mocked constant does not change in the component after jest.doMock.

Take a look at the minimal repo.

I have tried with mock instead of doMock- same error.

App.test.js

import React from "react"
import App from './App'
import '@testing-library/jest-dom'
import { render } from "@testing-library/react";

describe('testing app.js', () => {

  // To reset manually mocked values
  beforeEach(() => {
    jest.resetModules()
  });

  test("SET CONSTANT TO 1", () => {
    jest.doMock('./myConstants.js', () => ({
      CONSTANT: {
        NUMBER: 1
      }
    }))
    const { getByText, getByLabelText } = render(<App />)
    expect(getByText('1')).toBeInTheDocument()

  })

  test("SET CONSTANT TO 3", () => {
    jest.doMock('./myConstants.js', () => ({
      CONSTANT: {
        NUMBER: 3
      }
    }))
    const { getByText, getByLabelText } = render(<App />)
    expect(getByText('3')).toBeInTheDocument()

  })
})

App.js

import React from "react"
import { CONSTANT } from './myConstants.js'

console.log(CONSTANT)

const App = () => {
  return (
    <div>
      {CONSTANT.NUMBER}
    </div>
  );
}
export default App;

myConstants.js:

export const CONSTANT = { NUMBER: 2 }

Both of the tests above fail. The output from one of them is:

TestingLibraryElementError: Unable to find an element with the text: 3. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        <div>
          2
        </div>
      </div>
    </body>

      29 |     }))
      30 |     const { getByText, getByLabelText } = render(<App />)
    > 31 |     expect(getByText('3')).toBeInTheDocument()

Extended solution

Although the provided solution worked perfectly, I did not want to rewrite each component I was going to test (by adding require(...)). A workaround was to use import("./App").then((module)

import React from "react"
import App from './App'
import '@testing-library/jest-dom'
import { render } from "@testing-library/react";

describe('testing app.js', () => {

  // To reset manually mocked values
  beforeEach(() => {
    jest.resetModules()
  });

  jest.doMock('./myConstants.js', () => {
    return {
      __esModule: true,
      CONSTANT: {
        NUMBER: 1
      }
    }
  })

  test("SET CONSTANT TO 1", () => {


    // Wait for mock done
    return import('./myConstants.js').then((constants) => {
      console.log(constants.CONSTANT.NUMBER)
      expect(constants.CONSTANT.NUMBER).toBe(1)
      import("./App").then((module) => {
        const { getByText, getByLabelText } = render(<module.default />)
        expect(getByText('1')).toBeInTheDocument()
      })
    })
  })
})

Upvotes: 6

Views: 12095

Answers (2)

tmhao2005
tmhao2005

Reputation: 17514

I'm guessing we could also mutate the mocked value directly by still using jest.mock as following:

import React from "react"
import App from './App'
import '@testing-library/jest-dom'
import { render } from "@testing-library/react";
import { CONSTANT} from "./myConstants";

jest.mock('./myConstants.js', () => ({
  CONSTANT: {
    NUMBER: 0,
  }
}))

describe('testing app.js', () => {

  // To reset manually mocked values
  beforeEach(() => {
    jest.resetModules()
  });

  test("SET CONSTANT TO 1", () => {
    // Mutate directly mocked value by setting our desired value
    CONSTANT.NUMBER = 1;
    const { getByText, getByLabelText } = render(<App />)
    expect(getByText('1')).toBeInTheDocument()
  })

  test("SET CONSTANT TO 3", () => {
    // Likewise we set it as 3
    CONSTANT.NUMBER = 3;
    const { getByText, getByLabelText } = render(<App />)
    expect(getByText('4')).toBeInTheDocument()
  })
})

Upvotes: 3

tmhao2005
tmhao2005

Reputation: 17514

Using jest.doMock is quite complicated than you thought. Basically it requires you do a few steps as described https://jestjs.io/docs/en/jest-object#jestdomockmodulename-factory-options.

Which means you have to change your test to meet above requirements as:

test("SET CONSTANT TO 1", () => {
  jest.doMock('./myConstants.js', () => {
    return {
      __esModule: true,
      CONSTANT: {
        NUMBER: 1
      }
    }
  })
  
  // Wait for mock done
  return import('./myConstants.js').then(() => {
    const { getByText, getByLabelText } = render(<App />)
    expect(getByText('1')).toBeInTheDocument()  
  })
})

// While you also require your code right before using instead of `require` at the top level:

const App = () => {
  // Require to use it right before using it
  const { CONSTANT } = require('./myConstants.js');

  return (
    <div>
      {CONSTANT.NUMBER}
    </div>
  );
}

Upvotes: 4

Related Questions