How to mock window.navigator.language using jest

25,089

Solution 1

window.navigator and its properties are read-only, this is the reason why Object.defineProperty is needed to set window.navigator.language. It's supposed to work for changing property value multiple times.

The problem is that the component is already instantiated in beforeEach, window.navigator.language changes don't affect it.

Using Object.defineProperty for mocking properties manually will require to store original descriptor and restore it manually as well. This can be done with jest.spyOn. jest.clearAllMocks() wouldn't help for manual spies/mocks, it may be unneeded for Jest spies.

It likely should be:

let languageGetter;

beforeEach(() => {
  languageGetter = jest.spyOn(window.navigator, 'language', 'get')
})

it('should do thing 1', () => {
  languageGetter.mockReturnValue('de')
  wrapper = shallow(<Component {...props} />)
  expect(wrapper.state('currentLanguage')).toEqual('de')
})
...

Solution 2

Adding a little bit to Estus Flask's answer, you can also spy on your setup file:

In jest config file activate the setupFiles feature:

setupFiles: ['./test/mock-data/globals.js']

Then inside globals.js spy on the userAgent or any other property:

global.userAgent = jest.spyOn(navigator, 'userAgent', 'get');

Finally in your test mock the return value:

describe('abc', () => {
  global.userAgent.mockReturnValue(
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4)\
           AppleWebKit/600.1.2 (KHTML, like Gecko)\
           Version/13.0.0 Safari/600.1.2'
  );
  test('123', async () => {
    const result = await fnThatCallsOrUseTheUserAgent();
    expect(result).toEqual('Something');
  });
});

Solution 3

Alternatively

jest config file

setupFiles: ['./test/mock-data/globals.js']

globals.js

const navigator = { language: 'Chalcatongo Mixtec', ...anyOtherPropertiesYouNeed };

Object.defineProperty(window, 'navigator', {
   value: navigator,
   writable: true
});

then you can mutate freely in your individual test setup

Share:
25,089

Related videos on Youtube

the venom
Author by

the venom

Updated on July 09, 2022

Comments

  • the venom
    the venom almost 2 years

    I am trying to mock the window.navigator.language attribute in the browser in my jest unit tests so I can test that the content on my page is using the correct language

    I have found people online using this:

    Object.defineProperty(window.navigator, 'language', {value: 'es', configurable: true});

    I have set it right at the top of my test file and it is working there

    however, when I redefine in an individual test (and people set to make sure configurable was set to true) it wont redefine it and is just using the old value, does anyone know a way to definitely change it?

    beforeEach(() => {
        jest.clearAllMocks()
        Object.defineProperty(global.navigator, 'language', {value: 'es', configurable: true});
        wrapper = shallow(<Component {...props} />)
    })
    
      it('should do thing 1', () => {
          Object.defineProperty(window.navigator, 'language', {value: 'de', configurable: true});
          expect(wrapper.state('currentLanguage')).toEqual('de')
        })
    
    it('should do thing 2', () => {
      Object.defineProperty(window.navigator, 'language', {value: 'pt', configurable: true});
      expect(wrapper.state('currentLanguage')).toEqual('pt')
    })
    

    for these tests it is not changing the language to the new language I have set, always using the one at the top

    • Estus Flask
      Estus Flask over 5 years
      Please, don't describe what you're doing but provide actual code . stackoverflow.com/help/mcve is necessary for code questions.
    • manelescuer
      manelescuer over 5 years
      wasn't global.navigator that you could access and mock it? (maybe my memory fails)
    • the venom
      the venom over 5 years
      @estus have made changes
    • Estus Flask
      Estus Flask over 5 years
      What is Component?
    • the venom
      the venom over 5 years
      the component I'm mocking out. it doesn't hugely matter does it? as I'm more concerned that the object define thing is not resetting my property. once that is working i can fix the rest
    • Estus Flask
      Estus Flask over 5 years
      Of course, it matters because it's unclear how it uses window.navigator.language. I'm more concerned that the object define thing is not resetting my property - there's no evidence that the property isn't resetted. From the code you posted it's only clear that the component doesn't take changed property into account, possibly because tests were written the wrong way.
    • the venom
      the venom over 5 years
      I am logging the window.nav.lan property in component and it is not coming through with a different value for each test
    • the venom
      the venom over 5 years
      I know what to do with property in component, I just need it coming through correctly which it is not for now and I cant work out why
    • the venom
      the venom over 5 years
      if i told you <Component /> just logged out the property then can you tell me why it's still not correct
    • Estus Flask
      Estus Flask over 5 years
      just logged out the property - at which point? Constructor? componentDidMount? stackoverflow.com/help/mcve is required by SO rules because it allows to just understand the problem by reading the question and skip long negotiations on what should be posted and what should not.
    • the venom
      the venom over 5 years
      ok yeh it's in componentdidmount, basically in here I log the window.navigator.language and it is only giving me the value from the first Object.defineproperty at the top of the test file. it's not changing it for individual tests, it's nothing to do with the code as I've stripped it all down. for some reason jest is not resetting the property to be a different value every time :/
    • Estus Flask
      Estus Flask over 5 years
      for some reason jest is not resetting the property to be a different value every time - there are no evidences that this is true because you don't assert window.navigator.location, only its expected side effects, and the expectations were wrong. I provided the answer. Hope this helps.
  • the venom
    the venom over 5 years
    language property does not exist Ive tried changing window to global as well
  • Estus Flask
    Estus Flask over 5 years
    This depends on your setup. Changing to global can't help because navigator is browser feature. It should exists if Jest was configured to use JSDOM. Make sure that you did that. But I'm not sure how you previous attempt could work because there would be no window.navigator without JSDOM. Make sure your dependencies are up to date. I just successfully tested it with jest@23 and jsdom@11.
  • the venom
    the venom over 5 years
    I thought we use global because jest has no concept of a window?
  • Estus Flask
    Estus Flask over 5 years
    Jest doesn't. JSDOM does. jestjs.io/docs/en/configuration.html#testenvironment-string . window is actually global.window.
  • the venom
    the venom over 5 years
    what about that?
  • Estus Flask
    Estus Flask over 5 years
    I see no contradictions. JSDOM polyfills browser-specific globals. It's assigned as global properties` by Jest. Yes, global.window === global in this case. This is true only if Jest was configured to use JSDOM.
  • the venom
    the venom over 5 years
    ok regardless, I can't see why it's not working :(. the only differences is im on jest 22.4 and im rendering wrapper = shallow in the beforeeach rather than the test. other than that. exactly the same and i cant see how either of those would fix undefined :(
  • the venom
    the venom over 5 years
    ok upgrading jest did fix that. but now it's just coming through as en-US all the time
  • Estus Flask
    Estus Flask over 5 years
    rendering wrapper = shallow in the beforeeach rather than the test - there shouldn't be such differences. The answer contains the code that should work. That you do shallow in beforeEach is the exact reason why you have this problem, The problem is that the component is already instantiated in beforeEach, window.navigator.language changes don't affect it.
  • Estus Flask
    Estus Flask over 5 years
    Glad it helped.
  • Quang Van
    Quang Van over 2 years
    this leaks onto other tests I believe, you'd need an after to put it back to what it was before.
  • Jed Richards
    Jed Richards over 2 years
    This produces an error for me "userAgent is not declared configurable"