简体   繁体   English

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

[英]Jest stop test suite after first fail

I am using Jest for testing.我正在使用Jest进行测试。

What I want, is to stop executing the current test suite when a test in that test suite fails.我想要的是当该测试套件中的测试失败时停止执行当前测试套件。

The --bail option is not what I need, since it will stop other test suites after one test suite fails. --bail选项不是我需要的,因为它会在一个测试套件失败后停止其他测试套件。

I've made some kludge but it works for me.我做了一些 kludge 但它对我有用。

stopOnFirstFailed.js : 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);

Imports that file before all tests (before first test(...) ), for ex my index.test.js :在所有测试之前(在第一次test(...)之前test(...)导入该文件,例如我的index.test.js

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

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

That code marks all next tests failed with label globalFailure is TRUE when first error happens.当第一个错误发生时,该代码标记所有下一个测试failed ,标签globalFailure is TRUE

If you want to exclude failing , for ex.如果你想排除failing ,例如。 some cleanup tests you can do like this:您可以像这样执行一些清理测试:

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

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

It excludes whole group from failing .它排除了整个小组的failing

Tested with Node 8.9.1 and Jest 23.6.0使用 Node 8.9.1 和 Jest 23.6.0 进行测试

Thanks to this comment on github I was able to resolve this with a custom testEnvironment .感谢github上的这个评论,我能够使用自定义testEnvironment解决这个问题。 For this to work jest-circus need to be installed via npm / yarn .为此, jest-circus需要通过npm / yarn安装。
It's worth noting that jest will set jest-circus to the default runner with jest v27 .值得注意的是, jest 将 jest-circus 设置为 jest v27 的默认跑步者

First of all jest configuration needs to be adapted:首先需要调整jest配置:

jest.config.js

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

Then you need to implement a custom environment, which is already referenced by the config above:然后你需要实现一个自定义环境,上面的配置已经引用了:

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

NOTE笔记

I added registerTestEventHandler functionality which is not necessary for the fail fast feature, but I thought it's quite useful, especially if you used jasmine.getEnv() before and it works with async / await !我添加了registerTestEventHandler功能,这对于快速失败功能不是必需的,但我认为它非常有用,特别是如果您之前使用过jasmine.getEnv()并且它与async / await
You can register custom handler inside of your tests (eg beforeAll hook) like so:您可以测试中注册自定义处理程序(例如beforeAll钩子),如下所示:

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

When one test fails, other test statements in the same describe will be skipped.当一个test失败时,同一describe中的其他test语句将被跳过。 This also works for nested describe blocks, but the describe blocks must have different names.这也适用于嵌套的describe块,但describe必须具有不同的名称。

Executing following test:执行以下测试:

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", () => {})
})

will produce following log:将产生以下日志:

 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

This was my solution -- if there are major downsides please let me know, for my purposes it seems to work as intended这是我的solution ——如果有重大缺点,请告诉我,就我的目的而言,它似乎按预期工作

I only have one top-level describe block, for my purposes I want the entire test file to fail when one test fails我只有一个顶级描述块,出于我的目的,当一个测试失败时,我希望整个测试文件失败

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

Gives me a context (class attributes) to store global-esq variables给我一个上下文(类属性)来存储 global-esq 变量

const failEarlyTestRunner = new FailEarly(global.it);

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

overloads the test and it functions with my class' method (thus accessing the class attributes)过载testit的功能与我的类的方法(因此访问类属性)

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

results in:结果是:

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 {

Where each skipped test is failed w/ an error indicating the upstream, failing test每个跳过的测试都失败了,并有一个错误指示上游,失败的测试

As others have pointed out -- you've got to run jest with the --runInBand flag正如其他人指出的那样——你必须使用--runInBand标志来运行 jest

Hope this helps someone -- if there are meaningful drawbacks or better ways please comment;希望这对某人有所帮助 - 如果有有意义的缺点或更好的方法,请发表评论; I'm always happy to learn我总是乐于学习

hack the global.jasmine.currentEnv_.fail works for me.破解 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)
        })
      });

I guess that other tests in that suite depend on all previous tests be successful.我猜该套件中的其他测试依赖于所有以前的测试是否成功。 This is a bad practice in unit testing.这是单元测试中的一种不好的做法。 Try using beforeEach and afterEach to decouple individual test cases within a suite, so that they don't rely on each other.尝试使用beforeEachafterEach来分离套件中的各个测试用例,这样它们就不会afterEach

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM