35

The nose testing framework (for python) supports dynamically generating test cases at run-time (the following, from the documentation, results in five distinct test cases):

def test_evens():
    for i in range(0, 5):
        yield check_even, i, i*3

def check_even(n, nn):
    assert n % 2 == 0 or nn % 2 == 0

How can I achieve this result using javascript frameworks such as mocha or qunit? (I am not attached to any particular framework at this point.)

My use-case is writing a test runner to monitor several items on an external server. I would provide a list of resource URLs. Each test attempts to poll that resource and returns success or failure depending on what it finds. I have a prototype built in python (using nose) but would like to implement in node.js if I can. Eventually, this would be included in a CI setup.

1
  • 2
    node as in node.js? Maybe you should tag it, since just javascript will be interpreted as meaning javascript in browser. Commented Mar 17, 2014 at 23:04

7 Answers 7

42

Yes you can dynamically created test suites with cases using Mocha. I have installed mocha globally npm install -g mocha and I use should.

var should = require('should');

var foo = 'bar';

['nl', 'fr', 'de'].forEach(function(arrElement) {
  describe(arrElement + ' suite', function() {
    it('This thing should behave like this', function(done) {
      foo.should.be.a.String();
      done();
    });
    it('That thing should behave like that', function(done) {
      foo.should.have.length(3);
      done();
    });
  });
});
Sign up to request clarification or add additional context in comments.

5 Comments

good work, this worked for me. One quick point - you probably don't need to use aysnc. You can probably just use ['n1','fr','de'].forEach(). If you tests are synchronous, they will run in order, and no more slowly than otherwise. If your tests are asynchronous, then they will run asynchronously, but the forEach loop will keep loop, although it shouldn't matter that much either way, async.each or [].forEach should be about the same. Unless I am missing something and it() is blocking somehow?
@AlexMills you're right. I updated the code and removed the use of async.
NB! This works only for synchronously dynamically created test cases. In general case Mocha doesn't support that.
@polkovnikov.ph You're right that this accepted answer only works synchronously, which doesn't actually answer the OP's question about generating them from server requests (which are always async in nodejs). However, you can dynamically generate tests asynchronously using the hack in my answer below: stackoverflow.com/a/35793665/871990
@rob3c I have seen your answer, but it was a bit more complex than I was ready to do to put a library into my project. I just switched to node-tap, which works properly out of the box.
33
+50

If you want to dynamically create It() tests using data obtained asynchronously, you can (ab)use the before() hook with a placeholder It() test to ensure mocha waits until before() is run. Here's the example from my answer to a related question, for convenience:

before(function () {
    console.log('Let the abuse begin...');
    return promiseFn().
        then(function (testSuite) {
            describe('here are some dynamic It() tests', function () {
                testSuite.specs.forEach(function (spec) {
                    it(spec.description, function () {
                        var actualResult = runMyTest(spec);
                        assert.equal(actualResult, spec.expectedResult);
                    });
                });
            });
        });
});

it('This is a required placeholder to allow before() to work', function () {
    console.log('Mocha should not require this hack IMHO');
});

6 Comments

I tried all methods to accomplish this including trying to use the TDD framework for Mocha which has next to NO documentation. This method above works, but I just want to add that the outer "describe" is really just a placeholder. The inner test block is what counts. You will need to return a new promise once the dynamic tests are built and hook on to that promise with a new "then" method that will create an "after" block. So, there are two promises involved.
@rob3c If you add a after() block then you would notice, that it is not waiting for the before() to finish. The loop in the before() will surely create all those describe() blocks but it won't wait for them to finish. Any way to achieve that?
@Harry The before() call does complete before invoking any declared after() calls. However, any promises invoked within its callback may not have completed by then, which is just the nature of async javascript. It seems that your question is really a more general one about how javascript promises work and are coordinated, rather than how to generate mocha tests dynamically as asked in this question. It would be off-topic to discuss general javascript features further here, but I encourage you to create a new question if you don't find a satisfactory answer from your searching.
I'm trying to use this in cypress - any thoughts on why it wont work to load my expectation file and run the it() tests on it dynamically?
@rex I haven't used cypress.io before, but the FAQ says it's built on top of mocha and runs javascript in the browser. This hack works with regular mocha.js in the browser, so I don't know what's different about how cypress is wrapping it. Perhaps that's best asked as a separate question, since this question is about using mocha directly?
|
14

With Mocha 1.21.4, you can create suite/test at runtime in following way.

require('chai').should()

Mocha = require 'mocha'
Test = Mocha.Test
Suite = Mocha.Suite


mocha = new Mocha
suite = Suite.create mocha.suite, 'I am a dynamic suite'
suite.addTest new Test 'I am a dynamic test', ->
  true.should.equal true

mocha.run () ->
  console.log("done")

See https://gist.github.com/cybertk/fff8992e12a7655157ed for more details

Comments

13

It's worth noting that in addition to the accepted answer above, mocha's docs now include an example of how to achieve this. I've reproduced it below for posterity.

var assert = require('assert');

function add() {
  return Array.prototype.slice.call(arguments).reduce(function(prev, curr) {
    return prev + curr;
  }, 0);
}

describe('add()', function() {
  var tests = [
    {args: [1, 2],       expected: 3},
    {args: [1, 2, 3],    expected: 6},
    {args: [1, 2, 3, 4], expected: 10}
  ];

  tests.forEach(function(test) {
    it('correctly adds ' + test.args.length + ' args', function() {
      var res = add.apply(null, test.args);
      assert.equal(res, test.expected);
    });
  });
});

1 Comment

This is only for synchronous data.
6

Yep! Brilliant advice from Quanlong!

Here is my example of dynamic test generation with Node's readline module:

const Mocha = require('mocha');
var Test = Mocha.Test;
var Suite = Mocha.Suite;

var mocha = new Mocha();
var suite = Suite.create(mocha.suite, 'My test suite with dynamic test cases');

lineReader
    .on('line', function (line) {
        suite.addTest(new Test(line, function () {
            return true;
        }));
    })
    .on('close', function () {
        mocha.run();
    });

Comments

4

I like @rob3c's answer, but tried to simplify it a bit:

describe("Master test suite", function () {
  before(async function () {
    const rows = await mySQLQuery();

    describe(`Testing ${rows.length} rows`, function () {
      rows.forEach(function (row, index) {
        it(`Test row ${index}`, async function() {
          console.log("your row assertions go here")
        });
      });
    });
  });


  it("stub", async function(){})  // this is important!
});

2 Comments

This is awesome as it plays nicely with node-tdd when mocking requests with the "useNock" flag <3
Ensuring you have at least one test already defined within the describe block when your dynamic test relies on an asynchronous process (in my case reading files) was the bit I was missing, this answer makes that clearer. Thanks!
0

You can accomplish this by updating the tests property manually after the response is returned from the async method:

describe(`sometest`, function() {
  let results = null
  before(async () => {
    results = await someAsyncMethod();
    results.forEach((result, index) => {
      // to hold on to the new dynamic tests
      const newTest = it(result.name || `test ${index}`, () => {
        // do something here in test
      });
      // update the test objects before the main tests run
      this.tests.push(newTest);
    });
  });

  it(`sometest`, () => {
    expect(results.length).toBeGreaterThan(2);
  });

});

This doesn't use dynamic describes etc, just updates the current describe block before the main tests run!

2 Comments

Thanks, this solution worked for me and seems just a bit cleaner than the others. One thing to try in real world scenarios would be to separate the test as a separarte function but I did have problems with this.
Glad to have helped :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.