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

时间:2018-07-09 16:19:59

标签: javascript unit-testing testing jestjs

我正在使用Jest进行测试。

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

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

5 个答案:

答案 0 :(得分:2)

我做了些争吵,但对我有用。

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上进行了测试

答案 1 :(得分:0)

我猜该套件中的其他测试是否成功取决于所有先前的测试。这是单元测试中的不良做法。尝试使用beforeEachafterEach来解开套件中的各个测试用例,以免它们相互依赖。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

感谢 this comment on github,我能够使用自定义 testEnvironment 解决此问题。为此,jest-circus 需要通过 npm/yarn 安装。
值得注意的是 jest will set jest-circus to the default runner with jest v27.

首先需要适配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/{{ 1}}!
您可以在测试中内部注册自定义处理程序(例如 await 钩子),如下所示:

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

答案 4 :(得分: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);
  };
}

给我一​​个上下文(类属性)来存储 global-esq 变量

const failEarlyTestRunner = new FailEarly(global.it);

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

使用我的类的方法重载 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);
  })
})

结果:

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 标志来开玩笑

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