Mocha: `beforeEach` 和 `afterEach` 在嵌套的套件/上下文中运行

创建于 2013-06-28  ·  17评论  ·  资料来源: mochajs/mocha

没有关于 _when_ beforeEachafterEach将运行的文档。 我的直觉表明它应该在当前上下文中的每个describe / it块完成时运行before / after

但是,我注意到的行为是beforeEachafterEach在当前上下文中运行before / after _every_ it块以及所有嵌套的上下文。

我创建了一个概念证明来证明注意到的行为正在发生: https: //gist.github.com/twolfson/5883057#file -test-js

为方便起见,我将复制/粘贴脚本并在此处输出:

脚本

describe('An afterEach hook', function () {
  afterEach(function () {
    console.log('afterEach run!');
  });

  before(function () {
    console.log('before run!');
  });

  describe('in some nested contexts', function () {
    before(function () {
      console.log('nested before run!');
    });

    it('runs after this block', function () {
      console.log('nested it run!');

    });

    it('runs again after this block', function () {
      console.log('second nested it run!');
    });
  });
});

输出

before run!
nested before run!
nested it run!
afterEach run!
second nested it run!
afterEach run!

我直觉上预期的行为是afterEachsecond nested it之后运行一次。

before run!
nested before run!
nested it run!
second nested it run!
afterEach run!

我的直观功能用例是在上下文完成后清除this上下文。 当前实现将在每次断言后清除上下文。

afterEach(function () {
  var key;
  for (key in this) {
    delete this[key];
  }
});

describe('A banana', function () {
  before(function () {
    this.banana = new Banana();
  });

  describe('when peeled', function () {
    before(function () {
      this.peeledBanana = this.banana.peel();
    });

    it('is white', function () {
      assert.strictEqual(this.peeledBanana.color, 'white');
      // `afterEach` is invoked here, cleaning out `this`
    });

    it('is soft', function () {
      // `this.peeledBanana` is no longer defined since `afterEach` cleaned it out
      assert.strictEqual(this.peeledBanana.hardness, 'soft');
    });
  });
});

与此类似的示例是在测试套件的每个新上下文中打开一个数据库事务。

我可以通过在每个嵌套上下文中调用after来解决它,但我觉得框架中可能有一个钩子。

最有用的评论

对于遇到这种常见情况的每个人,我创建了这个辅助函数,它遍历当前块中的所有套件,并为每个套件添加一个beforeAll钩子:

function beforeEachSuite (fn) {
  before(function () {
    let suites = this.test.parent.suites || []
    suites.forEach(s => {
      s.beforeAll(fn)
      let hook = s._beforeAll.pop()
      s._beforeAll.unshift(hook)
    })
  })
}

这解锁了我大约一年前描述的用例:

describe('Create Article', () => {
  beforeEachSuite(() => console.log('CLEAN DB'))

  context('When Article is valid', () => {
    before(() => console.log("INSERTING VALID ARTICLE"))

    it('should not fail')
    it('should have some properties')    
    it('should send an email')  
  })

  context('When Article is not valid', () => {
    before(() => console.log("INSERTING NOT VALID ARTICLE"))

    it('should fail')
    it('should not send an email')  
  })
})

仍然不明白为什么这个用例不被认为是有用的或合法的。
也许这个解决方案可以在 PR 中转为beforeEachSuiteafterEachSuite

所有17条评论

对于那些好奇的人,这是我的用例的解决方法:

// Before anything else is run
before(function () {
  // Iterate over all of the test suites/contexts
  this.test.parent.suites.forEach(function bindCleanup (suite) {
    // Attach an afterAll listener that performs the cleanup
    suite.afterAll(function cleanupContext () {
      var key;
      for (key in this) {
        delete this[key];
      }
    });
  });
});

+1

afterEach在每个Runnable实例后运行; 在你的情况下,一个it()块。 如果您不想要嵌套行为,请不要嵌套您的测试。 改变这种行为不在桌面上。

如果您的第二个测试依赖于您在第一个测试中定义的某些内容,那么您的测试就是脏的并且您做错了。 您的测试执行的顺序应该无关紧要。 相应地编写您的测试,它们将更有价值。

此外,没有必要在 90% 的情况下在this上放置任何东西。

describe('A banana', function () {
  var banana;

  before(function () {
    banana = new Banana();
  });

  describe('when peeled', function () {
    var peeledBanana;

    beforeEach(function () {
      // I'm assuming peel() has no side-effects since it returns a new object.
      peeledBanana = banana.peel();
    });

    it('is white', function () {
      assert.strictEqual(peeledBanana.color, 'white');
    });

    it('is soft', function () {
      assert.strictEqual(peeledBanana.hardness, 'soft');
    });
  });
});

有没有办法在 describe() 级别而不是 it() 级别运行钩子方法? 在某些情况下它可能很有用。

@RathaKM用例是什么?

不确定这是否是一个很好的例子,但是,以下是一个很好的用例吗? @RathaKM随时纠正。

describe('A banana', function () {
  var banana;

  beforeEach(function () {
    banana = new Banana();
  });

  describe('when peeled', function () {
    var peeledBanana;

    before(function () {
      // Lets assume peel() HAS side-effects and doesn't return a new object.
      banana.peel();
      peeledBanana = banana;
    });

    it('is white', function () {
      assert.strictEqual(peeledBanana.color, 'white');
    });

    it('is soft', function () {
      assert.strictEqual(peeledBanana.hardness, 'soft');
    });
  });

  describe('when set on FIRE', function () {
    var flamingBanana;

    before(function () {
      // Same assumption as above
      banana.setOnFire();
      flamingBanana = banana
    });

    it('is hot', function () {
      assert.isAbove(flamingBanana.temperature, 9000);
    });
  });
});

@cpoonolly :感谢您的评论并将其带回来。 :) 您的示例是逻辑上期望在描述级别(BeforeEach)中工作的示例。 但是,这可以通过每个描述中的“before()”来实现。

我面临的问题在下面的例子中给出。

var testData = [{country: 'NIL'}],
dataDriven = require('data-driven'),
assert = require('assert');

describe('@Testing_For_Describe_Level_Hook_Method_To_Update_TestData@', function () {

    describe('<strong i="8">@Updated</strong> testData for US@', function () {
        before(function (done) {
            testData = [{country: 'US'}];
            done();
        });
        dataDriven(testData, function () {
            it('expecting updated testData for US', function (ctx, done) {
                assert.equal(ctx.country, 'US');
                done();
            });
        });
    });

    describe('<strong i="9">@Updated</strong> testData for UK@', function () {
        before(function (done) {
            testData = [{country: 'UK'}];
            done();
        });
        dataDriven(testData, function () {
            it('expecting updated testData for UK', function (ctx, done) {
                assert.equal(ctx.country, 'UK');
                done();
            });
        });
    });
});

在这里,测试套件将失败。 我们在 'before()' 钩子方法中更新 _testData_,该方法将在 '_it_' 块之前调用。 但是,我们在 _dataDriven()_ 中使用了 _testData_ 变量,该变量将在执行 '_it_' 块之前执行。

所以,我们在这里需要的是在 _'it'_ 块执行开始之前更新变量的地方。 可能我们需要在 'describe' 块(describe 级别挂钩方法)之前执行 _before()_ 挂钩方法。

希望,问题现在已经清楚了。

对此是否有任何额外的讨论?

为什么不可能得到这样的东西?

describe('Create Article', () => {
  beforeEach(() => console.log('CLEAN DB'))

  context('When Article is valid', () => {
    before(() => console.log("INSERTING VALID ARTICLE"))

    it('should not fail')
    it('should have some properties')    
    it('should send an email')  
  })

  context('When Article is not valid', () => {
    before(() => console.log("INSERTING NOT VALID ARTICLE"))

    it('should fail')
    it('should not send an email')  
  })
})
  • 在每个context之前清理数据库。
  • 在所有it之前设置场景

这不是一个合法的用例吗?

@marqu3z我正在尝试遵循您的用例。 这是合法的。 听起来问题是:before 钩子在 beforeEach 钩子之前运行? 但是你需要 before 钩子在 beforeEach 钩子之后运行?

(你真的想为每个测试用例清理数据库吗?似乎只有你的第一个测试用例会成功,其他的将没有数据。但我假设你想为每个测试用例清理数据库)。

这是我在给定 Mocha 的情况下解决您的用例的两次尝试:

(1) 使用 beforeEach,但翻转布尔值,所以 beforeEach 只运行一次(这有点糟糕)

describe('Create Article', () => {
  beforeEach(() => console.log('CLEAN DB'))

  context('When Article is valid', () => {
    let flip = true;
    beforeEach(() => flip && (flip = false; console.log('INSERTING VALID ARTICLE')))

    it('should not fail')
    it('should have some properties')    
    it('should send an email')  
  })

  context('When Article is not valid', () => {
   let flip = true;
    beforeEach(() => flip && (flip = false; console.log('INSERTING NOT VALID ARTICLE')))

    it('should fail')
    it('should not send an email')  
  })
})

(2) 另一种方法 - 这也好不到哪里去!

describe('Create Article', () => {

  let cleanDB = function(fn){
    return function(done){
        actuallyCleanDB(function(err){
            if(err) return done(err);
            fn(done);
       });
    }
  };

  context('When Article is valid', () => {
    before(() => console.log('INSERTING VALID ARTICLE'))
    it('should not fail', cleanDB(function(done){})
    it('should have some properties', cleanDB(function(done){}))    
    it('should send an email', cleanDB(function(done){})) 
  })

  context('When Article is not valid', () => {
    before(() => console.log('INSERTING NOT VALID ARTICLE')))
    it('should fail', cleanDB(function(done){}))
    it('should not send an email',cleanDB(function(done){}))  
  })
})

如果我理解你的问题,请告诉我,我会花更多的时间思考解决方案。 我的解决方案都不是很好,但我只是想确保我了解您遇到的问题。

@RathaKM

你在异步注册你的 it() 测试用例吗? 如果是这样,那是不允许的。 您必须同步注册describe/after/afterEach/before/beforeEach

// event loop tick X
 dataDriven(testData, function () {
     // it() must be called in the same event loop tick as X above
        it('expecting updated testData for UK', function (ctx, done) {
            assert.equal(ctx.country, 'UK');
            done();
       });
  });

另外,ctx 来自哪里? 也许我对最新的摩卡功能不是很了解,但我从未见过

it(foo, function(ctx, done){
   // where is ctx coming from?
});

我处于@marqu3z所描述的完全相同的场景中。 希望看到对describe级别beforeEach

对于遇到这种常见情况的每个人,我创建了这个辅助函数,它遍历当前块中的所有套件,并为每个套件添加一个beforeAll钩子:

function beforeEachSuite (fn) {
  before(function () {
    let suites = this.test.parent.suites || []
    suites.forEach(s => {
      s.beforeAll(fn)
      let hook = s._beforeAll.pop()
      s._beforeAll.unshift(hook)
    })
  })
}

这解锁了我大约一年前描述的用例:

describe('Create Article', () => {
  beforeEachSuite(() => console.log('CLEAN DB'))

  context('When Article is valid', () => {
    before(() => console.log("INSERTING VALID ARTICLE"))

    it('should not fail')
    it('should have some properties')    
    it('should send an email')  
  })

  context('When Article is not valid', () => {
    before(() => console.log("INSERTING NOT VALID ARTICLE"))

    it('should fail')
    it('should not send an email')  
  })
})

仍然不明白为什么这个用例不被认为是有用的或合法的。
也许这个解决方案可以在 PR 中转为beforeEachSuiteafterEachSuite

是的,我们需要这个。

有很多用例,比如https://github.com/mochajs/mocha/issues/911#issuecomment -316736991

+1

+1

我只是偶然发现这个线程寻找类似的东西。 所以对于任何偶然发现这篇文章的人......

我已经将@marqu3z在其自己的 NPM 包中概述的方法进行了调整: mocha-suite-hooks

因此,如果您发现自己处于before不够用而beforeEach太多的情况,请尝试beforeSuite

包裹仍然很原始,所以任何反馈将不胜感激!

此页面是否有帮助?
0 / 5 - 0 等级