如何使用jest模拟window.navigator.language?

42

我正在尝试在我的Jest单元测试中模拟浏览器中的window.navigator.language属性,以便测试我的页面内容是否使用了正确的语言。

我在网上找到了人们使用以下代码:

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

我已经将其设置在我的测试文件顶部,并且它在那里起作用。

然而,当我在单个测试中重新定义时(并且确保configurable设置为true),它不会被重新定义,仍然使用旧值。是否有人知道一种确定更改它的方法?

我正在尝试在我的Jest单元测试中模拟浏览器中的window.navigator.language属性,以便测试我的页面内容是否使用了正确的语言。我在网上找到了以下代码:Object.defineProperty(window.navigator, 'language', {value: 'es', configurable: true}); 我已经将其设置在我的测试文件顶部并生效。但是,当我在单个测试中重新定义时(并且确保configurable设置为true),它不会被重新定义,仍然使用旧值。请问是否有一种确切的方法可以更改它?

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')
})

对于这些测试,它没有改变语言为我设置的新语言,而是始终使用顶部的那一种语言。


@estus 已经进行了更改。 - the venom
什么是“组件”? - Estus Flask
如果我告诉你 <Component /> 刚刚记录了该属性,那么你能告诉我为什么它仍然不正确吗? - the venom
“just logged out the property” - 在哪个时刻?在构造函数中吗?在componentDidMount中吗?https://stackoverflow.com/help/mcve 是SO规则要求的,因为它允许通过阅读问题来理解问题,并跳过关于应该发布什么和不应该发布什么的长时间协商。 - Estus Flask
由于某些原因,Jest 每次都没有将属性重置为不同的值。这是没有证据支持的,因为您没有断言 window.navigator.location,只有其预期的副作用,并且预期是错误的。我提供了答案。希望这可以帮助您。 - Estus Flask
显示剩余7条评论
3个回答

67

window.navigator和它的属性是只读的,这就是为什么需要使用Object.defineProperty来设置window.navigator.language,它应该可以多次更改属性值。

问题是组件已经在beforeEach中被实例化,window.navigator.language的更改不会影响它。

使用Object.defineProperty手动模拟属性将需要存储原始描述符并手动恢复它。这可以使用jest.spyOn完成。jest.clearAllMocks()对于手动的spy/mock可能没有帮助,对于Jest spies可能是不必要的。

它可能应该是:

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')
})
...

1
语言属性不存在。我已经尝试将窗口更改为全局。 - the venom
1
这取决于你的设置。切换到全局无法帮助,因为navigator是浏览器功能。如果Jest已配置为使用JSDOM,则应该存在。确保你已经这样做了。但我不确定你之前的尝试如何能够工作,因为没有JSDOM就不会有window.navigator。确保你的依赖项是最新的。我刚刚使用jest@23和jsdom@11成功测试了它。 - Estus Flask
Jest不支持,但JSDOM支持。https://jestjs.io/docs/en/configuration.html#testenvironment-string。`window`实际上是`global.window`。 - Estus Flask
那个怎么样? - the venom
我没有看到任何矛盾。JSDOM填充了特定于浏览器的全局变量。在Jest中,它被分配为global属性。是的,在这种情况下,global.window === global。只有当Jest被配置为使用JSDOM时才成立。 - Estus Flask
显示剩余4条评论

3

在 Estus Flask 的回答中再进行一些补充,你还可以窥探你的设置文件:

在 jest 配置文件中激活 setupFiles 功能:

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

然后在 globals.js 文件中监听 userAgent 或其他属性:

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

最后在你的测试中模拟返回值:

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');
  });
});

这对我产生了一个错误:"userAgent未声明可配置"。 - Jed Richards

3

或者

Jest配置文件

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

globals.js

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

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

那么你可以在个人测试设置中自由地进行变异


我认为这会泄漏到其他测试中,你需要一个 after 将其恢复到之前的状态。 - Quang Van
1
它搞乱了我的navigator属性,但感谢setupFiles部分。在我的情况下,这个方法可行:Object.defineProperty(window.navigator, 'language', { value: 'pt-BR', writable: true, }) - rodvlopes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接