Mocha: `beforeEach` dan `afterEach` berjalan di suite/konteks bersarang

Dibuat pada 28 Jun 2013  ·  17Komentar  ·  Sumber: mochajs/mocha

Tidak ada dokumentasi tentang _when_ a beforeEach atau afterEach akan dijalankan. Intuisi saya menyatakan bahwa itu harus dijalankan before / after setiap describe / it blok dalam konteks saat ini selesai.

Namun, perilaku yang saya perhatikan, adalah beforeEach dan afterEach dijalankan before / after _every_ it blok dalam konteks saat ini dan semua konteks bersarang.

Saya telah membuat bukti konsep untuk menunjukkan bahwa perilaku yang diperhatikan sedang terjadi: https://Gist.github.com/twolfson/5883057#file -test-js

Untuk kenyamanan, saya akan menyalin/menempelkan skrip dan hasilnya di sini:

Naskah

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

Keluaran

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

Perilaku yang saya antisipasi secara intuitif adalah afterEach dijalankan sekali, setelah second nested it .

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

Kasus penggunaan saya untuk fungsionalitas intuitif adalah membersihkan konteks this setelah konteks selesai. Implementasi saat ini akan membersihkan konteks setelah setiap pernyataan.

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

Contoh yang sebanding dengan ini adalah membuka transaksi basis data di setiap konteks baru dari rangkaian pengujian.

Saya dapat mengatasinya dengan menerapkan after di setiap konteks bersarang tetapi saya merasa mungkin ada kaitan dalam kerangka kerja.

Komentar yang paling membantu

Untuk semua orang dengan skenario umum ini, saya membuat fungsi pembantu ini yang mengulangi semua suite di blok saat ini dan menambahkan kait beforeAll ke masing-masing suite:

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

Ini membuka kunci kasus penggunaan yang saya jelaskan hampir setahun yang lalu:

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

Masih tidak mengerti mengapa use case ini tidak dianggap berguna, atau sah.
Mungkin solusi ini dapat diubah menjadi PR untuk beforeEachSuite dan afterEachSuite

Semua 17 komentar

Bagi mereka yang penasaran, ini adalah solusi untuk kasus penggunaan saya:

// 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 dijalankan setelah setiap instance Runnable ; dalam kasus Anda, blok it() . Jika Anda tidak menginginkan perilaku bersarang, jangan susun pengujian Anda. Mengubah perilaku ini tidak ada di meja.

Jika pengujian kedua Anda bergantung pada sesuatu yang ditentukan dalam pengujian pertama Anda, pengujian Anda kotor dan Anda salah melakukannya. Urutan eksekusi pengujian Anda seharusnya tidak menjadi masalah. Tulis tes Anda sesuai dengan itu, dan itu akan lebih berharga.

Juga, tidak perlu memasukkan apa pun pada this 90% dari waktu.

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

Apakah ada cara untuk menjalankan metode kait di level deskripsikan () alih-alih level itu ()? Mungkin berguna dalam beberapa skenario.

@RathaKM Apa kasus penggunaan?

Tidak yakin apakah ini contoh yang bagus tetapi, apakah yang berikut ini akan menjadi kasus penggunaan yang baik untuk ini? @RathaKM jangan ragu untuk mengoreksi.

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 : terima kasih atas komentar Anda dan bawa kembali. :) Contoh Anda adalah apa yang diharapkan secara logis untuk bekerja di level deskripsi (Sebelum Setiap). Tapi, ini bisa dicapai dengan 'before()' dalam setiap deskripsi.

Masalah yang saya hadapi diberikan dalam contoh di bawah ini.

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

Di sini, rangkaian uji akan gagal. Kami memperbarui _testData_ dalam metode kait 'sebelum()' yang akan dipanggil tepat sebelum blok '_it_'. Namun, kita menggunakan variabel _testData_ dalam _dataDriven()_ yang akan dieksekusi sebelum mengeksekusi blok '_it_'.

Jadi, yang kita butuhkan di sini adalah tempat untuk memperbarui variabel sebelum eksekusi blok _'it'_ dimulai. Mungkin kita memerlukan eksekusi metode hook _before()_ tepat sebelum blok 'describe' (describe level hook method).

Harapan, masalahnya jelas sekarang.

Apakah ada diskusi tambahan tentang ini?

kenapa tidak mungkin mendapatkan sesuatu seperti ini?

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')  
  })
})
  • Bersihkan DB sebelum setiap blok context .
  • Siapkan skenario sebelum semua blok it

Bukankah ini kasus penggunaan yang sah?

@ marqu3z Saya mencoba mengikuti kasus penggunaan Anda. Ini sah. Sepertinya masalahnya adalah: sebelum kait berjalan sebelum kait beforeEach? tetapi Anda memerlukan kait sebelum untuk menjalankan setelah kait beforeEach?

(apakah Anda benar-benar ingin membersihkan DB untuk setiap test case? Sepertinya hanya test case pertama Anda yang akan berhasil, yang lain tidak akan memiliki data. Tapi saya akan menganggap Anda ingin membersihkan DB untuk setiap test case).

Berikut adalah dua upaya saya untuk menyelesaikan kasus penggunaan Anda yang diberikan Mocha apa adanya:

(1) gunakan beforeEach, tetapi balikkan boolean, jadi beforeEach hanya berjalan sekali (ini agak menyebalkan)

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) Cara lain untuk melakukan ini - ini tidak jauh lebih baik!

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

Tolong beri tahu saya jika saya memahami masalah Anda, dan saya akan menghabiskan lebih banyak siklus memikirkan solusi. Tidak satu pun dari solusi saya yang sangat bagus, tetapi saya hanya ingin memastikan bahwa saya memahami masalah yang Anda miliki.

@RathaKM

apakah Anda mendaftarkan kasus uji it() Anda secara tidak sinkron? Jika demikian, itu tidak diperbolehkan. Anda harus mendaftarkan describe/after/afterEach/before/beforeEach semuanya secara serempak.

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

juga, dari mana ctx berasal? Mungkin saya tidak up-to-date dengan fitur Mocha terbaru, tapi saya belum pernah melihatnya

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

Saya berada dalam skenario yang sama persis dengan yang dijelaskan @marqu3z . Akan senang melihat dukungan untuk level describe beforeEach

Untuk semua orang dengan skenario umum ini, saya membuat fungsi pembantu ini yang mengulangi semua suite di blok saat ini dan menambahkan kait beforeAll ke masing-masing suite:

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

Ini membuka kunci kasus penggunaan yang saya jelaskan hampir setahun yang lalu:

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

Masih tidak mengerti mengapa use case ini tidak dianggap berguna, atau sah.
Mungkin solusi ini dapat diubah menjadi PR untuk beforeEachSuite dan afterEachSuite

Ya tolong kami membutuhkan ini.

Ada banyak kasus penggunaan seperti https://github.com/mochajs/mocha/issues/911#issuecomment -316736991

+1

+1

Saya baru saja menemukan utas ini mencari sesuatu yang serupa. Jadi bagi siapa saja yang tersandung pada posting ini ...

Saya telah mengadaptasi pendekatan yang @marqu3z uraikan ke dalam paket NPM-nya sendiri: mocha-suite-hooks .

Jadi, jika Anda berada dalam situasi di mana before tidak cukup, dan beforeEach terlalu banyak, cobalah beforeSuite .

Paketnya masih cukup mentah, jadi umpan balik apa pun akan sangat dihargai!

Apakah halaman ini membantu?
0 / 5 - 0 peringkat