Mocha: `beforeEach`と` afterEach`はネストされたスイート/コンテキストで実行されています

作成日 2013年06月28日  ·  17コメント  ·  ソース: mochajs/mocha

beforeEachまたはafterEachがいつ実行されるかについてのドキュメントはありません。 私の直感では、現在のコンテキストで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!

私が直感的に予想する動作は、 second nested it後にafterEachを1回実行することです。

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

これにより、ほぼ1年前に説明したユースケースのロックが解除されます。

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

それでも、このユースケースが有用または正当であると見なされない理由はわかりません。
たぶん、このソリューションはbeforeEachSuiteafterEachSuite PRに変えることができます

全てのコメント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()ブロック。 ネストされた動作が必要ない場合は、テストをネストしないでください。 この動作を変更することはテーブルにありません。

2番目のテストが最初のテストで定義されたものに依存している場合、テストはダーティであり、間違っています。 テストを実行する順序は重要ではありません。 それに応じてテストを書いてください。そうすれば、より価値のあるものになります。

また、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');
    });
  });
});

it()レベルではなくdescribe()レベルでフックメソッドを実行する方法はありますか? シナリオによっては役立つ場合があります。

@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 :コメントありがとう

私が直面していた問題は、以下の例に示されています。

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

ここで、テストスイートは失敗します。 '_it_'ブロックの直前に呼び出される 'before()'フックメソッド内の_testData_を更新します。 ただし、「_ it_」ブロックを実行する前に実行される_dataDriven()_内の_testData_変数を使用します。

したがって、ここで必要なのは、_ 'it'_ブロックの実行が始まる前に変数を更新する場所です。 'describe'ブロック(describe level hookメソッド)の直前に_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ブロックのにDBをクリーンアップします。
  • すべてのitブロックのにシナリオを設定します

これは正当なユースケースではありませんか?

@ marqu3z私はあなたのユースケースに従おうとしています。 それは合法です。 問題は次のように聞こえます:beforeフックがbeforeEachフックの前に実行されますか? しかし、beforeEachフックの後に実行するにはbeforeフックが必要ですか?

(本当にすべてのテストケースのDBをクリーンアップしたいですか?最初のテストケースだけが成功し、他のテストケースにはデータがないようです。しかし、すべてのテストケースのDBをクリーンアップしたいと思います)。

Mochaをそのまま使用して、ユースケースを解決するための2つの試みを次に示します。

(1)beforeEachを使用しますが、ブール値を反転するため、beforeEachは1回だけ実行されます(これはちょっとひどいです)

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

これにより、ほぼ1年前に説明したユースケースのロックが解除されます。

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

それでも、このユースケースが有用または正当であると見なされない理由はわかりません。
たぶん、このソリューションはbeforeEachSuiteafterEachSuite PRに変えることができます

はい、これが必要です。

https://github.com/mochajs/mocha/issues/911#issuecomment-316736991のような多くのユースケースがあり

+1

+1

私はちょうど似たようなものを探してこのスレッドに出くわしました。 だから、この投稿につまずいた他の人には...

@ marqu3zが概説したアプローチを独自のNPMパッケージmocha-suite-hooks適合させました。

したがって、 beforeでは不十分で、 beforeEachが多すぎる状況に陥った場合は、 beforeSuiteを試してみてください。

パッケージはまだかなり生なので、フィードバックをいただければ幸いです。

このページは役に立ちましたか?
0 / 5 - 0 評価