Mocha: `beforeEach` и` afterEach` работают во вложенных наборах / контекстах

Созданный на 28 июн. 2013  ·  17Комментарии  ·  Источник: mochajs/mocha

Нет документации о том, _когда_ запускается beforeEach или afterEach . Моя интуиция подсказывает, что он должен запускаться before / after каждые describe / it блок в текущем контексте завершается.

Однако поведение, которое я замечаю, заключается в том, что beforeEach и afterEach запускаются before / after _every_ it block в текущем контексте. и все вложенные контексты.

Я создал доказательство концепции, чтобы продемонстрировать, что происходит заметное поведение: 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!

Я интуитивно предвижу, что afterEach запускается один раз после second 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 за beforeEachSuite и afterEachSuite

Все 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() . Если вам не нужно вложенное поведение, не вкладывайте тесты. Изменение этого поведения не является предметом обсуждения.

Если ваш второй тест зависит от чего-то определенного в вашем первом тесте, ваши тесты грязные, и вы делаете это неправильно. Порядок, в котором выполняются ваши тесты, не имеет значения. Напишите свои тесты соответственно, и они будут более ценными.

Кроме того, нет необходимости помещать что-либо на this 90% времени.

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 ()? Это может быть полезно в некоторых сценариях.

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

Здесь набор тестов не сработает. Мы обновляем _testData_ внутри метода перехвата 'before ()', который будет вызываться непосредственно перед блоком '_it_'. Но мы используем переменную _testData_ в _dataDriven () _, которая будет выполняться перед выполнением блока _it_.

Итак, что нам нужно, это место для обновления переменной до начала выполнения блока _'it'_. Может быть, нам нужно выполнение метода ловушки _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 Я пытаюсь следовать вашему

(вы действительно хотите очистить БД для каждого тестового примера? похоже, что только ваш первый тестовый пример будет успешным, другие не будут иметь данных. Но я предполагаю, что вы хотите очистить БД для каждого тестового примера).

Вот две мои попытки решить ваш вариант использования с 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? Возможно, я не в курсе последних функций Mocha, но я никогда не видел

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

Я использую тот же сценарий, который описал describe level 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 за beforeEachSuite и afterEachSuite

Да, пожалуйста, нам это нужно.

Есть много вариантов использования, например https://github.com/mochajs/mocha/issues/911#issuecomment -316736991.

+1

+1

Я только что наткнулся на эту ветку в поисках чего-то похожего. Так что всем, кто наткнется на этот пост ...

Я адаптировал подход, описанный @ marqu3z, к его собственному пакету NPM: mocha-suite-hooks .

Поэтому, если вы оказались в ситуации, когда before недостаточно, а beforeEach слишком много, попробуйте beforeSuite .

Пакет все еще довольно сырой, поэтому мы будем благодарны за любые отзывы!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги