Run async code before entire mocha test

2019-01-27 17:18发布

I'm looking for a way to run async code before the entire mocha test.

Here's an example of a test that uses an array of arguments & expectations and loops over all of the items in this array to produce function assertions.

var assert = require('assert')

/* global describe, it*/

var fn = function (value) {
  return value + ' ' + 'pancake'
}

var tests = [
  {
    'arg': 'kitty',
    'expect': 'kitty pancake'
  },
  {
    'arg': 'doggy',
    'expect': 'doggy pancake'
  },
]

describe('example', function () {
  tests.forEach(function (test) {
    it('should return ' + test.expect, function (){
      var value = fn(test.arg)
      assert.equal(value, test.expect)
    })
  })
})

Now, my question is how would this work if the tests value came from a promise, like this:

var assert = require('assert')
var Promise = require('bluebird')

/* global describe, it*/

var fn = function (value) {
  return value + ' ' + 'pancake'
}

function getTests () {
  return Promise.resolve('kitty pancake')
  .delay(500)
  .then(function (value) {
    return [
      {
        'arg': 'kitty',
        'expect': value
      },
      {
        'arg': 'doggy',
        'expect': 'doggy pancake'
      }
    ]
  })
}

getTests().then(function (tests) {
  describe('example', function () {
    tests.forEach(function (test) {
      it('should return ' + test.expect, function (){
        var value = fn(test.arg)
        assert.equal(value, test.expect)
      })
    })
  })  
})

also tried:

describe('example', function () {
  getTests().then(function (tests) {
    tests.forEach(function (test) {
      it('should return ' + test.expect, function (){
        var value = fn(test.arg)
        assert.equal(value, test.expect)
      })
    })
  })
})

However in this example none of the tests run because mocha doesn't recognize the describe statement because it's within a promise.

before / beforeEach won't do anything to help with a test in the format anyway unless the was a beforeTest hook that would supply mocha with the knowledge that there's an async operation that needs to be run before the entire test.

4条回答
三岁会撩人
2楼-- · 2019-01-27 17:59

I would move the async logic within the it call. Getting too fancy with unit tests is a code smell and likely to just irritate other developers when they not only have to debug and fix failing tests but have to debug and fix tests that aren't even defined and running because the fancy setup code has bugs. Try hard not to go there.

var assert = require('assert')
var Promise = require('bluebird')

/* global describe, it*/

var fn = function(value) {
  return value + ' ' + 'pancake'
}

function getTests() {
  return Promise.resolve('kitty pancake')
    .delay(500)
    .then(function(value) {
      return [
        {
          'arg': 'kitty',
          'expect': value
        },
        {
          'arg': 'doggy',
          'expect': 'doggy pancake'
        }
      ]
    })
}

describe('example', function() {
  it('should handle many things', function(done) {
    getTests().then(function(tests) {
      tests.forEach(function(test) {
        var value = fn(test.arg)
        assert.equal(value, test.expect, 'should return ' + test.expect)
      })
      done()
    })
  })
})
查看更多
疯言疯语
3楼-- · 2019-01-27 18:01

As an alternative to Daniel Perez's method you can also use the command line switch --delay and start the tests on the first run() call. By delaying the run() asynchronously you can register describes and its asynchronously beforehand. Note, though, that you can only call run() once, i.e. only in one test file. Thus I've created an async test runner in ./test/ and each async test in ./testAsync/:

// ./test/asyncRunner.js
"use strict";

const allAsyncPaths = [
  "test-featureA",
  "test-featureB",
].map(a => "../testAsync/" + a);

const allAsyncTestFunctions = allAsyncPaths.map(require);

Promise.resolve({
}).then(function() {
  const allPromises = allAsyncTestFunctions.map(a => a());
  return Promise.all(allPromises);
}).then(function() {
  run(); // mocha waits for run() because of --delay flag
}).catch(function(err) {
  console.error(err);
});

and

// ./testAsync/test-featureA.js
"use strict";
function asyncTestRegistrator() {

  return Promise.resolve({
  }).then(function() {
    return getTestsAsync();
  }).then(function(tests) {

  describe('example', function () {
    tests.forEach(function (test) {
      it('should return ' + test.expect, function (){
        var value = fn(test.arg);
        assert.equal(value, test.expect);
      });
    });
  });
}
module.exports = asyncTestRegistrator;
查看更多
霸刀☆藐视天下
4楼-- · 2019-01-27 18:11

I am not sure if there is any easy way to do this, but you could try to run Mocha programatically.

Here is a little dirty version of what this could look like, just to show the idea.

data.js

var Promise = require('bluebird')

module.exports.tests = []

function getTests () {
  return Promise.resolve('kitty pancake')
  .delay(500)
  .then(function (value) {
     module.exports.tests = [
      {
        'arg': 'kitty',
        'expect': value
      },
      {
        'arg': 'doggy',
        'expect': 'doggy pancake'
      }
    ]
  })
}

module.exports.getTests = getTests

test-launcher.js

var Mocha = require('mocha'),
    fs = require('fs'),
    path = require('path')

// First, you need to instantiate a Mocha instance.
var mocha = new Mocha()

// Then, you need to use the method "addFile" on the mocha
// object for each file.

// Here is an example:
fs.readdirSync('test').filter(function(file){
    // Only keep the .js files
    return file.substr(-3) === '.js'

}).forEach(function(file){
    // Use the method "addFile" to add the file to mocha
    mocha.addFile(
        path.join('test', file)
    )
})

// make sure your tests are loaded before running the tests
require('./data').getTests().then(function () {

  // Now, you can run the tests.
  mocha.run(function(failures){
    process.on('exit', function () {
      process.exit(failures)
    })
  })
})

test/index.js

var assert = require('assert')

var tests = require('../data').tests

var fn = function (value) {
  return value + ' ' + 'pancake'
}

describe('test', function () {
  describe('example', function () {
    tests.forEach(function (test) {
      it('should return ' + test.expect, function (){
        var value = fn(test.arg)
        assert.equal(value, test.expect)
      })
    })
  })
})

You can then run you rests by running test-launcher.js.

查看更多
在下西门庆
5楼-- · 2019-01-27 18:20

I would use the async/await with delay option as below:

setTimeout(async () => {
//get tests async
const tests = await getTests()

describe('example', async () => {

  tests.forEach((test) => {
   it(`test name: ${test.name} `, () => {
    console.log(test.name)
  })
 })
})

run()
}, 1000)
查看更多
登录 后发表回答