Mocha: `beforeEach` und `afterEach` laufen in verschachtelten Suiten/Kontexten

Erstellt am 28. Juni 2013  ·  17Kommentare  ·  Quelle: mochajs/mocha

Es gibt keine Dokumentation, _wenn_ ein beforeEach oder afterEach wird. Meine Intuition sagt, dass es before / after jeden describe / it Block im aktuellen Kontext ausgeführt werden sollte.

Das Verhalten, das ich jedoch bemerke, ist, dass beforeEach und afterEach werden before / after _jeder_ it Block im aktuellen Kontext und alle verschachtelten Kontexte.

Ich habe einen Machbarkeitsnachweis erstellt, um zu zeigen, dass das festgestellte Verhalten auftritt: https://gist.github.com/twolfson/5883057#file -test-js

Der Einfachheit halber werde ich das Skript kopieren / einfügen und hier ausgeben:

Skript

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

Ausgabe

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

Das Verhalten, das ich intuitiv erwarte, ist, dass afterEach einmal ausgeführt wird, nach dem second nested it .

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

Mein Anwendungsfall für die intuitive Funktionalität besteht darin, den this Kontext zu löschen, nachdem ein Kontext abgeschlossen ist. Die aktuelle Implementierung bereinigt den Kontext nach jeder Assertion.

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

Ein vergleichbares Beispiel hierfür wäre das Öffnen einer Datenbanktransaktion in jedem neuen Kontext einer Testsuite.

Ich kann es umgehen, indem ich after in jedem verschachtelten Kontext aufrufe, aber ich habe das Gefühl, dass es einen Haken im Framework geben könnte.

Hilfreichster Kommentar

Für alle mit diesem allgemeinen Szenario habe ich diese Hilfsfunktion erstellt, die über alle Suiten im aktuellen Block iteriert und jedem von ihnen einen beforeAll Hook hinzufügt:

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

Dies entsperrt den Anwendungsfall, den ich vor fast einem Jahr beschreibe:

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

Verstehe immer noch nicht, warum dieser Anwendungsfall nicht als nützlich oder legitim angesehen wird.
Vielleicht kann diese Lösung in eine PR für beforeEachSuite und afterEachSuite

Alle 17 Kommentare

Für Neugierige ist dies die Problemumgehung für meinen Anwendungsfall:

// 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 nach jeder Runnable Instanz ausgeführt; in Ihrem Fall ein it() Block. Wenn Sie das geschachtelte Verhalten nicht wünschen, verschachteln Sie Ihre Tests nicht. Dieses Verhalten zu ändern steht nicht auf dem Tisch.

Wenn Ihr zweiter Test von etwas abhängt, das in Ihrem ersten Test definiert wurde, sind Ihre Tests schmutzig und Sie machen es falsch. Die Reihenfolge, in der Ihre Tests ausgeführt werden, sollte keine Rolle spielen. Schreiben Sie Ihre Tests entsprechend, und sie werden wertvoller.

Außerdem ist es nicht notwendig, in 90 % der Fälle etwas auf this zu setzen.

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

Gibt es eine Möglichkeit, Hook-Methoden in der describe()-Ebene anstelle der it()-Ebene auszuführen? Es kann in einigen Szenarien nützlich sein.

@RathaKM Was ist der Anwendungsfall?

Ich bin mir nicht sicher, ob dies ein gutes Beispiel ist, aber wäre das Folgende ein guter Anwendungsfall dafür? @RathaKM gerne korrigieren.

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 : danke für deine Kommentare und bring sie zurück. :) Ihr Beispiel ist das, was logischerweise in der Beschreibungsebene (der BeforeEach) funktioniert. Dies könnte jedoch mit 'before()' in jedem Beschreibungstext erreicht werden.

Das Problem, mit dem ich konfrontiert war, ist im folgenden Beispiel angegeben.

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

Hier schlägt die Testsuite fehl. Wir aktualisieren die _testData_ innerhalb der Hook-Methode 'before()', die direkt vor dem '_it_'-Block aufgerufen wird. Wir verwenden jedoch die Variable _testData_ innerhalb von _dataDriven()_, die ausgeführt wird, bevor der '_it_'-Block ausgeführt wird.

Was wir hier also brauchen, ist ein Ort, um die Variable zu aktualisieren, bevor die Ausführung des _'it'_-Blocks beginnt. Vielleicht brauchen wir die _before()_ Hook-Methodenausführung kurz vor dem 'describe'-Block (describe level Hook-Methode).

Hoffe, das Thema ist jetzt klar.

Gab es hierzu zusätzliche Diskussionen?

warum bekommt man sowas nicht?

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')  
  })
})
  • Reinigen Sie den DB vor jedem context Block.
  • Richten Sie das Szenario vor allen it Blöcken ein

Ist das nicht ein legitimer Anwendungsfall?

@marqu3z Ich versuche, Ihrem Anwendungsfall zu folgen. Es ist legitim. Es klingt wie das Problem ist: der Before-Hook läuft vor dem beforeEach-Hook? aber Sie müssen den Before-Hook nach dem beforeEach-Hook ausführen?

(Möchten Sie wirklich die DB für jeden Testfall bereinigen? Scheint, als würde nur Ihr erster Testfall erfolgreich sein, die anderen haben keine Daten. Aber ich gehe davon aus, dass Sie die DB für jeden Testfall bereinigen möchten).

Hier sind meine beiden Versuche, Ihren Anwendungsfall zu lösen, wenn Mocha so ist:

(1) benutze ein beforeEach, aber drehe einen Boolean, so dass beforeEach nur einmal läuft (das ist irgendwie scheiße)

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) Der andere Weg, dies zu tun - das ist nicht viel besser!

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

Bitte lassen Sie es mich wissen, wenn ich Ihr Problem verstehe, und ich werde weitere Zyklen damit verbringen, über eine Lösung nachzudenken. Keine meiner Lösungen ist sehr gut, aber ich möchte nur sicherstellen, dass ich Ihr Problem verstehe.

@RathaKM

Registrieren Sie Ihre it()-Testfälle asynchron? Wenn ja, ist das nicht erlaubt. Sie müssen describe/after/afterEach/before/beforeEach alle synchron registrieren.

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

Und woher kommt ctx? Vielleicht bin ich nicht auf dem neuesten Stand der Mocha-Funktionen, aber ich habe es noch nie gesehen

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

Ich bin in genau dem gleichen Szenario wie @marqu3z beschrieben. Würde gerne Unterstützung für ein describe Level beforeEach

Für alle mit diesem allgemeinen Szenario habe ich diese Hilfsfunktion erstellt, die über alle Suiten im aktuellen Block iteriert und jedem von ihnen einen beforeAll Hook hinzufügt:

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

Dies entsperrt den Anwendungsfall, den ich vor fast einem Jahr beschreibe:

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

Verstehe immer noch nicht, warum dieser Anwendungsfall nicht als nützlich oder legitim angesehen wird.
Vielleicht kann diese Lösung in eine PR für beforeEachSuite und afterEachSuite

Ja, bitte, wir brauchen das.

Es gibt viele Anwendungsfälle wie https://github.com/mochajs/mocha/issues/911#issuecomment -316736991

+1

+1

Ich bin gerade über diesen Thread gestolpert und habe nach etwas ähnlichem gesucht. Also an alle anderen, die über diesen Beitrag stolpern...

Ich habe den Ansatz, den @marqu3z skizziert hat, an sein eigenes NPM-Paket angepasst: mocha-suite-hooks .

Wenn Sie sich also in einer Situation befinden, in der before nicht ausreicht und beforeEach zu viel ist, versuchen Sie es mit beforeSuite .

Das Paket ist noch ziemlich roh, daher wäre jedes Feedback sehr dankbar!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen