【问题标题】:Jest: test Intl.DateTimeFormat笑话:测试 Intl.DateTimeFormat
【发布时间】:2018-08-09 16:48:56
【问题描述】:

我想测试我编写的过滤器函数,它返回使用 Intl.DateTimeFormat('en-GB', options) 格式化的日期:

// module "date.js"
export default function (dateISOString) {
    const options = {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        timeZone: 'UTC'
    };
    let d = new Date(dateISOString);
    return new Intl.DateTimeFormat('en-GB', options).format(d);
};

这是我的测试,使用 Jest:

import date from '@/filters/date';
describe('date', () => {
    it('should format the date into dd/mm/yyyy', () => {
        expect(date('2014-02-11')).toEqual('11/02/2014');
    });
});

但它失败了:

Expected value to equal:
  "11/02/2014"
Received:
  "02/11/2014"

是否可以使用 Jest 测试(或模拟) Intl API? 问题似乎是由于浏览器和 Node 环境中 Impl API 的行为不同所致。

【问题讨论】:

    标签: javascript jestjs datetime-format


    【解决方案1】:

    这些都是令人费解的答案。最简单的就是像这样开玩笑地扩展原型。使用定义属性修改任何属性或函数

    Object.defineProperty(Intl.DateTimeFormat.prototype, 'formatToParts', {
      value: () => ['day', 'month', 'year'].map((type) => ({ type })),
    })
    
    

    【讨论】:

      【解决方案2】:

      如果您想在每次测试中更改区域,刚刚找到了解决方案。

      这里的好处是对 Intl 对象的更改很小,因为我们只是使用普通 api 来设置默认值

      const timezoneMock = function(zone: string) {
          const DateTimeFormat = Intl.DateTimeFormat
          jest
          .spyOn(global.Intl, 'DateTimeFormat')
          .mockImplementation((locale, options) => new DateTimeFormat(locale, {...options, timeZone: zone}))
        }
      
        afterEach(() => {
          jest.restoreAllMocks();
        })
      

      然后

        describe('when Europe/London', () => {
          it('returns local time', () => {
            timezoneMock('Europe/London')
         //etc.... 
      

      【讨论】:

        【解决方案3】:

        package.json 中添加 intl

         "intl": "*",
        

        jest.config.js

        module.exports = {
            moduleNameMapper: {
                Intl: '<rootDir>/node_modules/intl/'
            }
        };
        

        然后在 Date.spec.js

         describe(`Date by locale`, () => {
             beforeAll(() => {  global.Intl = require('intl'); });
            // Add your tests here. 
            // Add a temporary console.log to verify the correct output
         }
        

        【讨论】:

        【解决方案4】:

        您可以使用 polyfill,如 here 所述

        import IntlPolyfill from 'intl';
        import 'intl/locale-data/jsonp/ru';
        
        if (global.Intl) {
            Intl.NumberFormat = IntlPolyfill.NumberFormat;
            Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
        } else {
            global.Intl = IntlPolyfill;
        }
        

        【讨论】:

        • 这对我来说适用于节点 15,而 "test": "NODE_ICU_DATA=node_modules/full-icu jest --config jest.config.js" 没有
        【解决方案5】:

        我设法找到的唯一解决方案是安装full-icu,它似乎在测试过程中为节点提供了正确的语言环境。

        需要该软件包,因为默认情况下 node 仅附带一组有限的语言环境,此处解释:https://nodejs.org/docs/latest-v9.x/api/intl.html

        安装该软件包后,我必须采取的额外步骤是更改要使用的测试命令:

        "test": "NODE_ICU_DATA=node_modules/full-icu jest --config jest.config.js"

        我在几个不同的环境中遇到了这个问题。在 Mac OS 上本地运行测试时,以及在 CI 期间在 Docker 容器内运行测试时。

        有趣的是,通过 WebStorm 的 Jest 集成运行测试时,我不需要使用导出。看来 Intl 库的行为在节点中远非稳定。

        【讨论】:

        • 你成就了我的一天!
        • 如果您的操作系统已经提供了完整的 icu,您可以跳过安装它。例如,在 Mac OS 上,您可能在 /usr/share/icu 中有一个 icudt*.dat 文件,因此您可以将测试脚本设置为 "NODE_ICU_DATA=/usr/share/icu jest ..."
        • @JasonRice 这是在 Linux (bash) 和类似设备上设置环境变量的语法。 React 的人有一个nice summary 在其他 shell 中设置变量。也许试试"test": "set \"NODE_ICU_DATA=node_modules/full-icu\" &amp;&amp; jest ..."
        • 感谢您的链接,但知道如何在两种环境中执行此操作总是很高兴,尤其是在发布时。我最终只使用了命令前面的 npm cross-env。这似乎运作良好,不需要额外的配置。
        • 为了让它同时在 windows/linux 上运行,我使用了 cross-env 包:npmjs.com/package/cross-env 更多关于它如何工作的信息:basefactor.com/…
        猜你喜欢
        • 2019-09-06
        • 2019-08-17
        • 2021-06-13
        • 1970-01-01
        • 2019-07-05
        • 2018-12-23
        • 2020-12-15
        • 2019-04-05
        • 2020-07-12
        相关资源
        最近更新 更多