第一次失败后停止测试套件

问题描述 投票:0回答:7

我正在使用 Jest 进行测试。

我想要的是,当当前测试套件中的测试失败时,停止执行该测试套件。

--bail
选项不是我需要的,因为它会在一个测试套件失败后停止其他测试套件。

javascript unit-testing testing jestjs
7个回答
8
投票

我有连续且复杂的测试场景,如果该套件的其中一项测试失败,则没有必要继续测试套件。但我没有设法将它们标记为已跳过,因此它们显示为已通过。

我的测试套件示例:

describe('Test scenario 1', () => {

test('that item can be created', async () => {
    expect(true).toBe(false)
})

test('that item can be deleted', async () => {
    ...
})
...

我将其更改为以下内容:

let hasTestFailed = false
const sequentialTest = (name, action) => {
    test(name, async () => {        
      if(hasTestFailed){
        console.warn(`[skipped]: ${name}`)} 
      else {
          try {         
            await action()} 
          catch (error) {           
            hasTestFailed = true
            throw error}            
      }
    })
  }
describe('Test scenario 1', () => {
    
sequentialTest('that item can be created', async () => {
        expect(true).toBe(false)
})
    
sequentialTest('that item can be deleted', async () => {
        ...
})

如果第一个测试失败,则下一个测试将不会运行,但它们将获得“通过”状态。

报告将如下所示:

  • 测试场景 1 > 可以创建该项目 - 失败
  • 测试场景 1 > 该项目可以删除 - 通过

这并不理想,但在我的情况下是可以接受的,因为我只想在报告中看到失败的测试。


6
投票

感谢 github 上的评论,我能够通过自定义

testEnvironment
解决此问题。为此,
jest-circus
需要通过
npm
/
yarn
安装。
值得注意的是,jest 将使用 jest v27 将 jest-circus 设置为默认运行器。

首先需要调整 jest 配置:

jest.config.js

module.exports = {
  rootDir: ".",
  testRunner: "jest-circus/runner",
  testEnvironment: "<rootDir>/NodeEnvironmentFailFast.js",
}

然后你需要实现一个自定义环境,上面的配置已经引用了它:

NodeEnvironmentFailFast.js

const NodeEnvironment = require("jest-environment-node")

class NodeEnvironmentFailFast extends NodeEnvironment {
  failedDescribeMap = {}
  registeredEventHandler = []

  async setup() {
    await super.setup()
    this.global.testEnvironment = this
  }

  registerTestEventHandler(registeredEventHandler) {
    this.registeredEventHandler.push(registeredEventHandler)
  }

  async executeTestEventHandlers(event, state) {
    for (let handler of this.registeredEventHandler) {
      await handler(event, state)
    }
  }

  async handleTestEvent(event, state) {
    await this.executeTestEventHandlers(event, state)

    switch (event.name) {
      case "hook_failure": {
        const describeBlockName = event.hook.parent.name

        this.failedDescribeMap[describeBlockName] = true
        // hook errors are not displayed if tests are skipped, so display them manually
        console.error(`ERROR: ${describeBlockName} > ${event.hook.type}\n\n`, event.error, "\n")
        break
      }
      case "test_fn_failure": {
        this.failedDescribeMap[event.test.parent.name] = true
        break
      }
      case "test_start": {
        if (this.failedDescribeMap[event.test.parent.name]) {
          event.test.mode = "skip"
        }
        break
      }
    }

    if (super.handleTestEvent) {
      super.handleTestEvent(event, state)
    }
  }
}

module.exports = NodeEnvironmentFailFast

注意

我添加了

registerTestEventHandler
功能,这对于快速失败功能来说不是必需的,但我认为它非常有用,特别是如果您之前使用过
jasmine.getEnv()
并且它与
async
/
await
一起使用!
您可以在测试中注册自定义处理程序inside(例如
beforeAll
钩子),如下所示:

// testEnvironment is globally available (see above NodeEnvironmentFailFast.setup)
testEnvironment.registerTestEventHandler(async (event) => {
  if (event.name === "test_fn_failure") {
    await takeScreenshot()
  }
})

当一个

test
失败时,同一
test
中的其他
describe
语句将被跳过。这也适用于嵌套
describe
块,但
describe
必须 有不同的名称。

执行以下测试:

describe("TestJest 3 ", () => {
  describe("TestJest 2 ", () => {
    describe("TestJest 1", () => {
      beforeAll(() => expect(1).toBe(2))
      test("1", () => {})
      test("1.1", () => {})
      test("1.2", () => {})
    })

    test("2", () => expect(1).toBe(2))
    test("2.1", () => {})
    test("2.2", () => {})
  })

  test("3", () => {})
  test("3.1", () => expect(1).toBe(2))
  test("3.2", () => {})
})

将产生以下日志:

 FAIL  suites/test-jest.spec.js
  TestJest 3 
    ✓ 3
    ✕ 3.1 (1 ms)
    ○ skipped 3.2
    TestJest 2 
      ✕ 2
      ○ skipped 2.1
      ○ skipped 2.2
      TestJest 1
        ○ skipped 1
        ○ skipped 1.1
        ○ skipped 1.2

  ● TestJest 3  › TestJest 2  › TestJest 1 › 1

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 1

      2 |   describe("TestJest 2 ", () => {
      3 |     describe("TestJest 1", () => {
    > 4 |       beforeAll(() => expect(1).toBe(2))
        |                                 ^
      5 |       test("1", () => {})
      6 |       test("1.1", () => {})
      7 |       test("1.2", () => {})

      at suites/test-jest.spec.js:4:33

  ● TestJest 3  › TestJest 2  › TestJest 1 › 1.1

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 1

      2 |   describe("TestJest 2 ", () => {
      3 |     describe("TestJest 1", () => {
    > 4 |       beforeAll(() => expect(1).toBe(2))
        |                                 ^
      5 |       test("1", () => {})
      6 |       test("1.1", () => {})
      7 |       test("1.2", () => {})

      at suites/test-jest.spec.js:4:33

  ● TestJest 3  › TestJest 2  › TestJest 1 › 1.2

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 1

      2 |   describe("TestJest 2 ", () => {
      3 |     describe("TestJest 1", () => {
    > 4 |       beforeAll(() => expect(1).toBe(2))
        |                                 ^
      5 |       test("1", () => {})
      6 |       test("1.1", () => {})
      7 |       test("1.2", () => {})

      at suites/test-jest.spec.js:4:33

  ● TestJest 3  › TestJest 2  › 2

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 1

       8 |     })
       9 | 
    > 10 |     test("2", () => expect(1).toBe(2))
         |                               ^
      11 |     test("2.1", () => {})
      12 |     test("2.2", () => {})
      13 |   })

      at Object.<anonymous> (suites/test-jest.spec.js:10:31)

  ● TestJest 3  › 3.1

    expect(received).toBe(expected) // Object.is equality

    Expected: 2
    Received: 1

      14 | 
      15 |   test("3", () => {})
    > 16 |   test("3.1", () => expect(1).toBe(2))
         |                               ^
      17 |   test("3.2", () => {})
      18 | })
      19 | 

      at Object.<anonymous> (suites/test-jest.spec.js:16:31)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 6 skipped, 1 passed, 9 total
Snapshots:   0 total
Time:        0.638 s, estimated 1 s


3
投票

我做了一些拼凑,但它对我有用。

stopOnFirstFailed.js

/**
 * This is a realisation of "stop on first failed" with Jest
 * @type {{globalFailure: boolean}}
 */

module.exports = {
    globalFailure: false
};

// Injects to jasmine.Spec for checking "status === failed"
!function (OriginalSpec) {
    function PatchedSpec(attrs) {
        OriginalSpec.apply(this, arguments);

        if (attrs && attrs.id) {
            let status = undefined;
            Object.defineProperty(this.result, 'status', {
                get: function () {
                    return status;
                },
                set: function (newValue) {
                    if (newValue === 'failed') module.exports.globalFailure = true;
                    status = newValue;
                },
            })
        }
    }

    PatchedSpec.prototype = Object.create(OriginalSpec.prototype, {
        constructor: {
            value: PatchedSpec,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });

    jasmine.Spec = PatchedSpec;
}(jasmine.Spec);

// Injects to "test" function for disabling that tasks
test = ((testOrig) => function () {
    let fn = arguments[1];

    arguments[1] = () => {
        return module.exports.globalFailure ? new Promise((res, rej) => rej('globalFailure is TRUE')) : fn();
    };

    testOrig.apply(this, arguments);
})(test);

在所有测试之前导入该文件(在第一个

test(...)
之前),例如我的
index.test.js

require('./core/stopOnFirstFailed'); // before all tests

test(..., ()=>...);
...

当第一个错误发生时,该代码用标签

failed
标记所有接下来的测试
globalFailure is TRUE

如果您想排除

failing
,例如。您可以这样做一些清理测试:

const stopOnFirstFailed = require('../core/stopOnFirstFailed');

describe('some protected group', () => {
    beforeAll(() => {
        stopOnFirstFailed.globalFailure = false
    });
    test(..., ()=>...);
    ...

它将整个组排除在

failing
之外。

使用 Node 8.9.1 和 Jest 23.6.0 进行测试


0
投票

这是我的

solution
——如果有重大缺点,请告诉我,就我的目的而言,它似乎按预期工作

我只有一个顶级描述块,出于我的目的,我希望当一个测试失败时整个测试文件都会失败

export class FailEarly {
  msg: string | undefined;
  failed: boolean = false;
  jestIt: jest.It;

  constructor(jestIt: jest.It) {
    this.jestIt = jestIt;
  }

  test = (name: string, fn: jest.EmptyFunction, timeout?: number) => {
    const failEarlyFn = async () => {
      if (this.failed) {
        throw new Error(`failEarly: ${this.msg}`);
      }

      try {
        await fn();
      } catch (error) {
        this.msg = name;
        this.failed = true;
        throw error;
      }
    };

    this.jestIt(name, failEarlyFn, timeout);
  };
}

给我一个上下文(类属性)来存储全局esq变量

const failEarlyTestRunner = new FailEarly(global.it);

const test = failEarlyTestRunner.test;
const it = failEarlyTestRunner.test;

使用我的类的方法重载

test
it
函数(从而访问类属性)

describe('my stuff', () => {
  it('passes', async () => {
    expect(1).toStrictEqual(1);
  })

  test('it fails', async () => {
    expect(1).toStrictEqual(2);
  })

  it('is skipped', async () => {
    expect(1).toStrictEqual(1);
  })
})

结果:

my stuff
  ✓ can create a sector (2 ms)
  ✕ it fails (2 ms)
  ✕ is skipped (1 ms)


  ● my stuff › it fails

    expect(received).toStrictEqual(expected) // deep equality

    Expected: 2
    Received: 1

    > ### |       expect(1).toStrictEqual(2);
          |                 ^
      ### |     });


  ● my stuff › is skipped

    failEarly: it fails

      69 |     const failEarlyFn = async () => {
      70 |       if (this.failed) {
    > 71 |         throw new Error(`failEarly: ${this.msg}`);
         |               ^
      72 |       }
      73 |
      74 |       try {

每个跳过的测试都失败,并出现错误指示上游测试失败

正如其他人指出的那样——你必须用

--runInBand
标志来开玩笑

希望这对某人有帮助——如果有有意义的缺点或更好的方法,请评论;我总是很高兴学习


0
投票

我发现这个 Jest 环境具有完全相同的用法

https://www.npmjs.com/package/jest-environment-steps

通过启用测试文件中的步骤

/**
 * @jest-environment steps
 */

describe("Tests in this describe runs as steps", () => {
    test("one", () => {});

    describe("inner describe", () => {
       test("inner one", () => {});
       test("inner two", () => {});
    })
    test("two", () => {});
})

inner one
失败,跳过
inner two
two

的执行

0
投票

对于 2024 年发现此问题的人,请查看 https://www.npmjs.com/package/jest-environment-steps,因为它为此提供了一个简单的解决方案。


-1
投票

破解 global.jasmine.currentEnv_.fail 对我有用。

      describe('Name of the group', () => {

        beforeAll(() => {

          global.__CASE_FAILED__= false

          global.jasmine.currentEnv_.fail = new Proxy(global.jasmine.currentEnv_.fail,{
            apply(target, that, args) {
              global.__CASE__FAILED__ = true
              // you also can record the failed info...
              target.apply(that, args)
              }
            }
          )

        })

        afterAll(async () => {
          if(global.__CASE_FAILED__) {
            console.log("there are some case failed");
            // TODO ...
          }
        })

        it("should xxxx", async () => {
          // TODO ...
          expect(false).toBe(true)
        })
      });
© www.soinside.com 2019 - 2024. All rights reserved.