Mocha: `beforeEach` y` afterEach` se ejecutan en conjuntos / contextos anidados

Creado en 28 jun. 2013  ·  17Comentarios  ·  Fuente: mochajs/mocha

No hay documentación sobre _cuando_ se ejecutará un beforeEach o afterEach . Mi intuición dice que debe ejecutarse before / after cada describe / it bloque en el contexto actual se completa.

Sin embargo, el comportamiento que estoy notando es que beforeEach y afterEach se ejecutan before / after _todos_ it bloque en el contexto actual y todos los contextos anidados.

He creado una prueba de concepto para demostrar que se está produciendo el comportamiento observado: https://gist.github.com/twolfson/5883057#file -test-js

Para mayor comodidad, copiaré / pegaré el script y la salida aquí:

Texto

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

Producción

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

El comportamiento que anticipo intuitivamente es afterEach para ejecutarse una vez, después de second nested it .

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

Mi caso de uso para la funcionalidad intuitiva es limpiar el contexto this después de que se completa un contexto. La implementación actual limpiará el contexto después de cada afirmación.

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

Un ejemplo comparable a esto sería abrir una transacción de base de datos en cada nuevo contexto de un conjunto de pruebas.

Puedo solucionarlo invocando after en cada contexto anidado, pero siento que podría haber un gancho en el marco.

Comentario más útil

Para todos con este escenario común, creé esta función auxiliar que itera sobre todas las suites en el bloque actual y agrega un gancho beforeAll a cada una de ellas:

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

Esto desbloquea el caso de uso que describo hace casi un año:

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

Aún no entiendo por qué este caso de uso no se considera útil o legítimo.
Tal vez esta solución se pueda convertir en un PR por beforeEachSuite y afterEachSuite

Todos 17 comentarios

Para aquellos curiosos, esta es la solución para mi caso de uso:

// 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 ejecuta después de cada Runnable instancia; en su caso, un bloque it() . Si no desea el comportamiento anidado, no anide sus pruebas. La alteración de este comportamiento no está sobre la mesa.

Si su segunda prueba depende de algo definido en su primera prueba, sus pruebas están sucias y lo está haciendo mal. El orden en el que se ejecutan las pruebas no debería importar. Escriba sus pruebas en consecuencia y serán más valiosas.

Además, no es necesario poner nada en this 90% del tiempo.

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

¿Hay alguna forma de ejecutar métodos de enlace en el nivel describe () en lugar de en el nivel it ()? Puede resultar útil en algunos escenarios.

@RathaKM ¿Cuál es el caso de uso?

No estoy seguro de si este es un buen ejemplo, pero ¿sería el siguiente un buen caso de uso para esto? @RathaKM no dude en corregirlo.

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 : gracias por tus comentarios y tráelo de vuelta. :) Su ejemplo es lo que lógicamente esperaba que funcionara en el nivel de descripción (BeforeEach). Pero, esto podría lograrse con 'antes ()' dentro de cada descripción.

El problema al que me enfrentaba se da en el siguiente ejemplo.

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

Aquí, la suite de pruebas fallará. Actualizamos el método de gancho _testData_ dentro de 'before ()' que será llamado justo antes del bloque '_it_'. Pero usamos la variable _testData_ dentro de _dataDriven () _ que se ejecutará antes de ejecutar el bloque '_it_'.

Entonces, lo que necesitamos aquí es un lugar para actualizar la variable antes de que comience la ejecución del bloque _'it'_. Puede ser que necesitemos la ejecución del método de gancho _before () _ justo antes del bloque 'describe' (método de gancho de nivel de descripción).

Espero, el problema está claro ahora.

¿Ha habido alguna discusión adicional sobre esto?

¿Por qué no es posible conseguir algo como esto?

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')  
  })
})
  • Limpie la base de datos antes de cada bloque context .
  • Configure el escenario antes de todos los bloques it

¿No es este un caso de uso legítimo?

@ marqu3z Estoy tratando de seguir su caso de uso. Es de fiar. Parece que el problema es: el gancho before se ejecuta antes que el gancho beforeEach? pero necesitas que el gancho before se ejecute después del gancho beforeEach?

(¿Realmente desea limpiar la base de datos para cada caso de prueba? Parece que solo su primer caso de prueba tendrá éxito, los otros no tendrán datos. Pero asumiré que desea limpiar la base de datos para cada caso de prueba).

Aquí están mis dos intentos de resolver su caso de uso dado Mocha como está:

(1) use un beforeEach, pero cambie un booleano, por lo que beforeEach solo se ejecuta una vez (esto apesta un poco)

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) La otra forma de hacer esto, ¡no es mucho mejor!

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

Por favor, avíseme si entiendo su problema y pasaré más ciclos pensando en una solución. Ninguna de mis soluciones es muy buena, pero solo quiero asegurarme de entender el problema que tiene.

@RathaKM

¿Está registrando sus casos de prueba de it () de forma asincrónica? Si es así, eso no está permitido. Debe registrar describe/after/afterEach/before/beforeEach todo sincrónicamente.

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

Además, ¿de dónde viene ctx? Tal vez no esté actualizado con las últimas funciones de Mocha, pero nunca he visto

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

Estoy en el mismo escenario que describió @ marqu3z . Me encantaría ver soporte para un describe nivel beforeEach

Para todos con este escenario común, creé esta función auxiliar que itera sobre todas las suites en el bloque actual y agrega un gancho beforeAll a cada una de ellas:

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

Esto desbloquea el caso de uso que describo hace casi un año:

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

Aún no entiendo por qué este caso de uso no se considera útil o legítimo.
Tal vez esta solución se pueda convertir en un PR por beforeEachSuite y afterEachSuite

Sí, por favor, necesitamos esto.

Hay muchos casos de uso como https://github.com/mochajs/mocha/issues/911#issuecomment -316736991

+1

+1

Me encontré con este hilo buscando algo similar. Entonces, para cualquier otra persona que se tropiece con esta publicación ...

He adaptado el enfoque que @ marqu3z describió en su propio paquete de NPM: mocha-suite-hooks .

Entonces, si se encuentra en una situación en la que before no es suficiente y beforeEach es demasiado, pruebe beforeSuite .

El paquete todavía está bastante crudo, por lo que cualquier comentario será muy apreciado.

¿Fue útil esta página
0 / 5 - 0 calificaciones