笑话:在单元测试中禁用控制台的更好方法

Posted

技术标签:

【中文标题】笑话:在单元测试中禁用控制台的更好方法【英文标题】:Jest: Better way to disable console inside unit tests 【发布时间】:2017-11-12 01:17:39 【问题描述】:

我想知道是否有更好的方法来禁用控制台错误 特定的 Jest 测试(即,恢复原始控制台 在每次测试之前/之后)。

这是我目前的做法:

describe("Some description", () => 
  let consoleSpy;

  beforeEach(() => 
    if (typeof consoleSpy === "function") 
      consoleSpy.mockRestore();
    
  );

  test("Some test that should not output errors to jest console", () => 
    expect.assertions(2);

    consoleSpy = jest.spyOn(console, "error").mockImplementation();

    // some function that uses console error
    expect(someFunction).toBe("X");
    expect(consoleSpy).toHaveBeenCalled();
  );

  test("Test that has console available", () => 
    // shows up during jest watch test, just as intended
    console.error("test");
  );
);

有没有更简洁的方法来完成同样的事情? 我想避免使用spyOn,但mockRestore 似乎只能使用它

谢谢!

【问题讨论】:

我不小心隐藏了一个真正的错误。理想情况下,您应该尝试做的第一件事是诊断警告或错误。如果它真正是良性的,下面有很多答案可以帮助隐藏它。 @DevinRhode,这是一个不错的建议但是在某些情况下,控制台输出是测试的一部分,例如测试函数中的错误处理代码。有时您可能希望调用控制台记录的第 3 方代码,而不是模拟它。 【参考方案1】:

由于每个测试文件都在其自己的线程中运行,因此如果您想对一个文件中的所有测试禁用它,则无需恢复它。出于同样的原因,您也可以只写

console.log = jest.fn()
expect(console.log).toHaveBeenCalled();

【讨论】:

感谢您提供有关此事的信息。确实有道理:) 我一直在寻找一种方法,使其仅在特定测试中以这种方式进行,而不必恢复它(我最初认为这是默认情况下的行为),但我猜 beforeEach 可以解决问题。 但是同一文件中的下一个测试仍然会对其进行模拟,对吗?视情况而定,这可能并不理想。【参考方案2】:

对于特定的规范文件,Andreas 的已经足够好了。下面的设置将禁止所有测试套件的 console.log 语句,

jest --silent

(或)

要自定义warn, info and debug,您可以使用以下设置

__tests__/setup.js jest-preload.js 配置在setupFilesAfterEnv

global.console = 
  log: jest.fn(), // console.log are ignored in tests

  // Keep native behaviour for other methods, use those to print out things in your own tests, not `console.log`
  error: console.error,
  warn: console.warn,
  info: console.info,
  debug: console.debug,
;

jest.config.js

module.exports = 
    verbose: true,
    setupTestFrameworkScriptFile: "<rootDir>/__tests__/setup.js",
;

Jest v24.x 注意:setupTestFrameworkScriptFile 已弃用,取而代之的是 setupFilesAfterEnv.

module.exports = 
    verbose: true,
    setupFilesAfterEnv: ["<rootDir>/__tests__/setup.js"],
;

【讨论】:

嗨! setupTestFrameworkScriptFile 已弃用,取而代之的是 setupFilesAfterEnv Mocking global.console 确实是一种简单的方法,可以通过任何配置的setupFilesAfterEnv 来完成。请注意模拟 console 对象的所有本机方法,否则您可能会遇到其他意外错误。 请注意,如果您想检查模拟(或清除它),您需要将其称为console.log,例如expect(console.log).toBeCalledTimes(1)。 您还可以将"silent": true 选项添加到jest.config.js 文件中【参考方案3】:

我发现上面的答案是:在所有测试套件中抑制 console.log 会引发错误,因为它正在替换整个全局 console对象。

这种有点类似的方法对我来说适用于 Jest 22+:

package.json

"jest": 
  "setupFiles": [...],
  "setupTestFrameworkScriptFile": "<rootDir>/jest/setup.js",
  ...

jest/setup.js

jest.spyOn(global.console, 'log').mockImplementation(() => jest.fn());

使用此方法,只有console.log 被模拟,其他console 方法不受影响。

【讨论】:

【参考方案4】:

另一种方法是使用process.env.NODE_ENV这样,人们可以在运行测试时有选择地选择显示(或不显示)什么:

if (process.env.NODE_ENV === 'development') 
  console.log('Show output only while in "development" mode');
 else if (process.env.NODE_ENV === 'test') 
  console.log('Show output only while in "test" mode');

const logDev = msg => 
  if (process.env.NODE_ENV === 'development') 
    console.log(msg);
  

logDev('Show output only while in "development" mode');

这需要将此配置放在package.json:

"jest": 
  "globals": 
    "NODE_ENV": "test"
  

请注意,这种方法不是对原始问题的直接解决方案,但只要有可能用上述条件包装 console.log,就会给出预期的结果。

【讨论】:

问题的作者是如何在测试中禁用console.log。这个解决方案不是最优的。 对于复制粘贴:根据您的需要将=== 替换为!==。我多年来一直在使用这种方法,并且效果很好,但我确实会根据自己的需要进行调整。 不回答实际问题。 这是一个 hacky 解决方案,不可自定义。如果仅针对特定测试而不是其他测试禁用怎么办?【参考方案5】:

对我来说,一种更清晰/干净的方式(读者需要很少的 jest API 知识来理解正在发生的事情),就是手动执行 mockRestore 所做的事情:

// at start of test you want to suppress
const consoleLog = console.log;
console.log = jest.fn();

// at end of test
console.log = consoleLog;

【讨论】:

还需要覆盖console.info、console.error、console.warn等 @michael-liquori 为什么需要重启console.log?我认为在每次描述之后,模拟都会被清除 @Jhonatan 我认为每次描述后都不清楚,尽管我最近没有对此进行测试以确定。根据jest docs,有一个clearMocksresetMocks 配置选项,但它们都默认为false即使设置为true,它们都不会真正恢复初始实现。而且,考虑到这是一个可以在某些时候更改的配置选项,我认为最好手动清理以确保您的测试不会在将来造成问题。【参考方案6】:

如果您只想针对特定测试进行此操作:

beforeEach(() => 
  jest.spyOn(console, 'warn').mockImplementation(() => );
);

【讨论】:

这太棒了! 它在我的测试中不起作用,我在测试期间仍然有一些console.warn。多次测试,不防弹 Noice Toit Smort! 我不知道为什么这个答案有这么多的赞成票。这是在每次测试之前禁用控制台功能的好方法(正如名称 beforeEach 所暗示的那样),但它没有回答 OP 的问题,即“如何在 specific 中禁用控制台错误玩笑测试”。 @fenix.shadow 它很容易适应在单个测试中进行。可以在beforeEach 中完成的任何事情都可以在it 中完成。至于人们说它不起作用……它对我有用。您可能还想捕获 Vue Test Utils 的默认错误处理程序引发的错误。【参考方案7】:
beforeAll(() => 
    jest.spyOn(console, 'log').mockImplementation(() => );
    jest.spyOn(console, 'error').mockImplementation(() => );
    jest.spyOn(console, 'warn').mockImplementation(() => );
    jest.spyOn(console, 'info').mockImplementation(() => );
    jest.spyOn(console, 'debug').mockImplementation(() => );
);

【讨论】:

【参考方案8】:

感谢@Raja 的最佳答案。这是我正在使用的(我会发表评论,但不能在评论中分享多行代码块)。

使用 jest v26,我收到此错误:

We detected setupFilesAfterEnv in your package.json.

Remove it from Jest configuration, and put the initialization code in src/setupTests.js:
This file will be loaded automatically.

因此,我不得不从我的 jest 配置中删除 setupFilesAfterEnv,并将其添加到 src/setupTests.js

// https://***.com/questions/44467657/jest-better-way-to-disable-console-inside-unit-tests
const nativeConsoleError = global.console.error

global.console.error = (...args) => 
  if (args.join('').includes('Could not parse CSS stylesheet')) 
    return
  
  return nativeConsoleError(...args)

【讨论】:

【参考方案9】:

由于jest.spyOn 对此不起作用(过去可能有),因此我求助于jest.fn 进行手动模拟恢复,如Jest docs 中指出的那样。这样,您就不会错过任何在特定测试中没有被经验忽略的日志。

const consoleError = console.error

beforeEach(() => 
  console.error = consoleError
)

test('with error', () => 
  console.error = jest.fn()
  console.error('error') // can't see me
)

test('with error and log', () => 
  console.error('error') // now you can
)

【讨论】:

【参考方案10】:

奇怪的是上面的答案(除了Raja 的好答案,但我想分享其他人失败的奇怪方式以及如何清除模拟所以没有人浪费我所做的时间)似乎成功地创建了模拟但是不要禁止将日志记录到控制台。

两者

const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => );

global console = 
   warn: jest.fn().mockImplementation(() => );

成功安装了模拟(我可以使用 expect(console.warn).toBeCalledTimes(1) 并且它通过了)但它仍然输出警告,即使模拟实现似乎应该替换默认值(这是在 jsdom 环境中) )。

最终我找到了一个解决问题的技巧,并将以下内容放入您的配置中加载了 SetupFiles 的文件中(请注意,有时我发现 global.$ 在将 jquery 放入全局上下文时对我不起作用,所以我只是设置在我的设置中,我所有的全局变量都是这样)。

const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(() => );
const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => );
const consoleDebug = jest.spyOn(console, 'debug').mockImplementation(() => );
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => );


Object.defineProperty(global, 'console', value: 
                                            warn: consoleWarn,
                                            log: consoleLog,
                                            debug: consoleDebug,
                                            error: consoleError);

感觉很难看,然后我不得不在每个测试文件中放入如下代码,因为在 SetupFiles 引用的文件中没有定义 beforeEach(也许您可以将两者都放入 SetupFilesAfterEnv 但我没有尝试过)。

beforeEach(() => 
  console.warn.mockClear();
);

【讨论】:

以上是关于笑话:在单元测试中禁用控制台的更好方法的主要内容,如果未能解决你的问题,请参考以下文章

带有笑话和酶的反应单元测试

反应笑话单元测试用例TypeError:无法读取未定义的属性'then'

在 SwiftUI 单元测试中禁用模拟器

无法读取未定义的属性“getters” - 带有笑话的 VueJS 单元测试

暂时禁用单个 Python 单元测试

Kotlin coroutine单元测试的更好方法