JavaScript / Jest:如何在测试失败时显示测试用例的日志?

ki0zmccv  于 12个月前  发布在  Jest
关注(0)|答案(5)|浏览(176)

我正在使用Selenium开发一个端到端的测试套件,其中的测试用例是使用jest测试运行器用JavaScript编写的。
我的问题是selenium经常在某些东西不起作用的时候突然失败,而且几乎没有解释它失败的原因。不用说,像这样的调试测试可能相当困难。
我正在寻找一种方法来记录每个测试用例,这样我就知道测试在哪里失败了,但是如果测试实际上失败了,只在测试输出中显示这些日志(为了不污染来自测试的控制台输出与大量不必要的日志)。
所以我想做的是:

describe(() => {
    it('case 1', async () => {
        // this log should only be printed on the console if this test fails
        logger.info('message from case 1');
        // ...
    });

    it('case 2', () => {
        logger.info('message from case 2');
        // ...
    });
});

字符串
因此,如果case 1中的测试失败而case 2没有失败,我会在控制台输出中看到message from case 1(理想情况下,就在该测试用例的错误之前),而不是 * message from case 2
jest可以做到这一点吗?我可以自由使用任何日志库。

r55awzrz

r55awzrz1#

我遇到了同样的问题,找不到明确的解决方案。似乎是it's low on Facebook's to do list,所以这里有一个解决方案。它使用了我找到的herehere的代码片段。这个想法是,在每个jest运行之前,你设置一个消息存储,并全局覆盖控制台,将所有日志转移到那里。每次测试之后,你检查测试是否失败,如果失败,把隐藏的信息打印出来

package.json

"jest": {
    ...
    "verbose": true,
    "setupFilesAfterEnv": ["<rootDir>/test/setup.js"],
    ...
  }

字符串

setup.js

const util = require('util')

global.consoleMessages = []

// get information about the current test
jasmine.getEnv().addReporter({
    specStarted: result => jasmine.currentTest = result,
    specDone: result => jasmine.currentTest = result,
})

function squirrelAway(text, logger) {
    // use error to get stack trace
    try {
        throw new Error('stacktrace')
    } catch (err) {
        let trace = err.stack.split('\n')
        trace.shift()   // removes Error: stacktrace
        trace.shift()   // removes squirrelAway() call from the "throw" command
        trace.shift()   // removes console logger call in the console override
        consoleMessages.push({logger: logger, payload: text, stacktrace: trace.join('\n')})
    }
}

const orig = console
global.console = {...console,
    // use jest.fn() to silence, comment out to leave as it is
    log: (text => squirrelAway(text, orig.log)),
    error: (text => squirrelAway(text, orig.error)),
    warn: (text => squirrelAway(text, orig.warn)),
    info: (text => squirrelAway(text, orig.info)),
    debug: (text => squirrelAway(text, orig.debug))
}

global.afterEach(() => {
    // this includes tests that got aborted, ran into errors etc.
    let failed = (jasmine && jasmine.currentTest
                  && Array.isArray(jasmine.currentTest.failedExpectations)) ?
                 jasmine.currentTest.failedExpectations.length>0 : true
    //orig.log(`test "${jasmine.currentTest.fullName}" finished. failed? ${failed}`)
    if (failed) {
        //orig.log(`Logging for "${jasmine.currentTest.fullName}" start`)
        consoleMessages.forEach(msg => {
            if (typeof msg.payload === 'object' || typeof msg.payload === 'function') {
                msg.payload = util.inspect(msg.payload, false, null, true)
            }
            msg.logger.call(msg.logger, msg.payload + '\n' + msg.stacktrace)
        })
        //orig.log(`Logging for "${jasmine.currentTest.fullName}" end`)
    }
    consoleMessages = []
})


如果你需要做任何其他清理,你可以在你的测试文件中“扩展”afterEach,像这样:

some.test.js

const globalAfterEach = afterEach
afterEach((globalAfterEach) => {
    // do things here
    globalAfterEach()
    // or do things here
})

缺点

  • 因为我们覆盖了控制台,日志记录的堆栈跟踪丢失了,当控制台被调用时,jest使用的堆栈跟踪只包含安装文件中被覆盖的console.log(/debug/error/...)调用的堆栈。所以为了获得原始堆栈跟踪,我们抛出一个错误。然后可以将其附加到正在记录的文本中。不是特别漂亮,但可以工作。
oug3syen

oug3syen2#

有一些方法可以巧妙地处理expect调用,让您知道故障发生在哪里。

const location = "case 1 failed";
const result = someFunction();  
expect({result: result, location}).toEqual({result: "hello", location});

字符串
现在,如果someFunction()返回的不是“hello”,它会告诉你位置值,因为它会抱怨预期的结果。
只有当您得到一个Jest错误,但没有从正常的expect失败消息中获得足够的信息,并且您需要更多的细节时,这才是有用的。

xwmevbvl

xwmevbvl3#

这可能是一个糟糕的做法,但我注意到expect().toBe()或其他东西会在不相等的情况下抛出一个Error,你可以先捕获它,然后记录它。
这是示例代码,当我尝试测试是每一个文本消息翻译的语言。

test('All message has translate', () => {

  allAvailableText.forEach((key) => {

    const languagesOnTheKey = Object.keys(text[key]).sort()
    try {
      expect(languagesOnTheKey).toStrictEqual(languages)
    } catch (error) {
      console.log(key, languagesOnTheKey)
    }
    expect(languagesOnTheKey).toStrictEqual(languages)

  })
})

字符串
这是最快的方法,也是最容易阅读的方法,因为选择的答案让我很难理解。

9jyewag0

9jyewag04#

测试环境可以很好地处理这个问题。它缓冲所有的控制台消息,根据测试对它们进行分组,并且只在测试失败时才显示它们。环境相对于设置文件或报告器的好处是,它可以有选择地应用于特定的测试,而将全局控制台单独留给所有其他测试。

./tests/testEnvironment.js

const NodeEnvironment = require('jest-environment-node');
const colors = require('colors');

class TestEnvironment extends NodeEnvironment {
    constructor(config, context) {
        super(config, context);
    }

    async setup() {
        await super.setup();

        this.global.consoleItems = [];

        this.global.console = { ...console,
            original: console,
            log: ((message) => this.global.consoleItems.push({type: 'log', message })),
            error: ((message) => this.global.consoleItems.push({type: 'error', message })),
            warn: ((message) => this.global.consoleItems.push({type: 'warn', message })),
            info: ((message) => this.global.consoleItems.push({type: 'info', message })),
            debug: ((message) => this.global.consoleItems.push({type: 'debug', message })),
        };
    }

    async teardown() {
        this.global.console = this.global.console.original;
        await super.teardown();
    }

    async handleTestEvent(event, state) {
        if (event.name === 'test_done' && event.test.errors.length > 0) {
            let test = event.test;
            let fullTestName = event.test.name;
            while (test.parent != null && test.parent.name !== 'ROOT_DESCRIBE_BLOCK') {
                fullTestName = test.parent.name + ' › ' + fullTestName;
                test = test.parent;
            }
            this.global.console.original.log(colors.bold.red('Console messages for failed test ' + fullTestName));
            this.global.consoleItems.forEach((item) => {
                if (item.type === 'log') {
                    this.global.console.original.log('    - ' + item.message);
                } else if (item.type === 'error') {
                    this.global.console.original.error('    - ' + item.message);
                } else if (item.type === 'warn') {
                    this.global.console.original.warn('    - ' + item.message);
                } else if (item.type === 'info') {
                    this.global.console.original.info('    - ' + item.message);
                } else if (item.type === 'debug') {
                    this.global.console.original.debug('    - ' + item.message);
                }
            });
            this.global.console.original.log('\n');
        }
        if (event.name === 'test_done') {
            this.global.consoleItems = [];
        }
    }

}

module.exports = TestEnvironment;

字符串
对于每个测试套件,使用此环境需要以下注解:

/**
 * @jest-environment ./tests/testEnvironment
 */

rlcwz9us

rlcwz9us5#

我修改了siilike的方法,它工作得很好。我仍然有一些落后的控制台消息,我猜Jest没有捕获到。
之前:

...
➤ YN0000: [premiser-ui]: PASS src/reducers/ui/ui.test.js
➤ YN0000: [premiser-ui]:   ● Console
➤ YN0000: [premiser-ui]:
➤ YN0000: [premiser-ui]:     console.warn
➤ YN0000: [premiser-ui]:       Unsupported extension environment.
➤ YN0000: [premiser-ui]:
➤ YN0000: [premiser-ui]:       143 |   extension = new Extension(window.browser)
➤ YN0000: [premiser-ui]:       144 | } else {
➤ YN0000: [premiser-ui]:     > 145 |   logger.warn("Unsupported extension environment.")
➤ YN0000: [premiser-ui]:           |          ^
➤ YN0000: [premiser-ui]:       146 |   extension = new Extension()
➤ YN0000: [premiser-ui]:       147 | }
➤ YN0000: [premiser-ui]:       148 |
➤ YN0000: [premiser-ui]:
➤ YN0000: [premiser-ui]:       at Object.<anonymous> (howdju-client-common/lib/extension.js:145:10)
➤ YN0000: [premiser-ui]:       at Object.<anonymous> (howdju-client-common/lib/index.js:3:1)
➤ YN0000: [premiser-ui]:
➤ YN0000: [premiser-ui]:
➤ YN0000: [premiser-ui]: Test Suites: 7 passed, 7 total
➤ YN0000: [premiser-ui]: Tests:       27 passed, 27 total
➤ YN0000: [premiser-ui]: Snapshots:   0 total
➤ YN0000: [premiser-ui]: Time:        6.598 s

字符串
之后:

...
➤ YN0000: [premiser-ui]: PASS src/reducers/ui/ui.test.js
➤ YN0000: [premiser-ui]:
➤ YN0000: [premiser-ui]: Test Suites: 7 passed, 7 total
➤ YN0000: [premiser-ui]: Tests:       27 passed, 27 total
➤ YN0000: [premiser-ui]: Snapshots:   0 total
➤ YN0000: [premiser-ui]: Time:        4.863 s


Jest更新:

// jest.config.js

module.exports = {
  ...
  reporters: [
    '<rootDir>/jest/OutputConsoleOnFailureOnlyReporter.js',
    '<rootDir>/node_modules/@jest/reporters/build/SummaryReporter.js',
  ]
}
// <packageRoot>/jest/OutputConsoleOnFailureOnlyReporter.js

const { DefaultReporter } = require('@jest/reporters')
const chalk = require('chalk')

const TITLE_BULLET = chalk.bold('\u25cf ');

/** A custom Jest reporter that skips console output for passing tests. */
class OutputConsoleOnFailureOnlyReporter extends DefaultReporter {
    constructor() {
        super(...arguments)
    }

  logWithoutConsole(message) {
    // If the message looks like captured console logging from here:
    // https://github.com/facebook/jest/blob/c660c1688d4a1898ba00143abad22ad2785f350b/packages/jest-reporters/src/DefaultReporter.ts#L223
    // then skip it.
    if (message.startsWith(`  ${TITLE_BULLET}Console`)) {
      return
    }
    process.stderr.write(`${message}\n`);
  }

    printTestFileHeader(_testPath, config, result) {
        const originalLog = this.log
        // If the test didn't fail, then don't log its console output.
        if (result.numFailingTests === 0 && !result.testExecError) {
            this.log = this.logWithoutConsole
        }
        super.printTestFileHeader(...arguments)
        // Restore the original method.
        this.log = originalLog
    }
}

module.exports = OutputConsoleOnFailureOnlyReporter

的字符串

相关问题