├── Makefile ├── .gitignore ├── package.json ├── LICENSE ├── test ├── datadriven.test.js └── mocha │ └── tests.js ├── README.md └── index.js /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: test 3 | 4 | test: test-unit 5 | 6 | test-unit: 7 | @./node_modules/mocha/bin/mocha test/*.js 8 | 9 | .PHONY: test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | 17 | */.idea 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-driven", 3 | "version": "1.4.0", 4 | "description": "data driven testing for mocha", 5 | "keywords": [ 6 | "mocha", 7 | "test", 8 | "bdd", 9 | "tdd", 10 | "data", 11 | "data driven" 12 | ], 13 | "author": "Matthew Todd ", 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/fluentsoftware/data-driven.git" 17 | }, 18 | "main": "./index", 19 | "engines": { 20 | "node": ">= 0.4.x" 21 | }, 22 | "scripts": { 23 | "test": "make test" 24 | }, 25 | "dependencies": { 26 | }, 27 | "devDependencies": { 28 | "should": "*", 29 | "mocha": "*" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Fluent Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/datadriven.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var Mocha = require('mocha'); 7 | var should = require('should'); 8 | 9 | describe('data driven extension', function() { 10 | 11 | it('should run data driven tests for each data item in the list', function(done) { 12 | var mocha = new Mocha 13 | , passed = [] 14 | , failed = []; 15 | 16 | function reporter(runner) { 17 | 18 | runner.on('pass', function(test) { 19 | passed.push(test.title); 20 | }); 21 | 22 | runner.on('fail', function(test) { 23 | failed.push(test.title); 24 | }); 25 | } 26 | 27 | mocha.addFile('test/mocha/tests.js'); 28 | mocha.reporter(reporter); 29 | mocha.timeout(100); 30 | mocha.run(function() { 31 | passed.should.eql([ 32 | 'should not affect tests outside of the dataDriven function', 33 | 'should run the data driven function with key1', 34 | 'should allow async data driven testing with key1 value value1', 35 | 'should allow async data driven testing with key2 value value2', 36 | 'should handle deep value and deeper value lookups', 37 | 'should allow returning successful promises', 38 | 'should pass appropriate this object to sync test function', 39 | 'should pass appropriate this object to async test function' 40 | ]); 41 | 42 | failed.should.eql([ 43 | 'should allow timeouts for async data driven testing with key1', 44 | 'should run the data driven function with key2', 45 | 'should allow timeouts for async data driven testing with key2', 46 | 'should allow returning rejected promises', 47 | 'should allow timing out promises' 48 | ]); 49 | 50 | done(); 51 | }); 52 | }); 53 | 54 | }); 55 | 56 | -------------------------------------------------------------------------------- /test/mocha/tests.js: -------------------------------------------------------------------------------- 1 | var dataDriven = require('../../'); 2 | var should = require('should'); 3 | 4 | describe('data-driven', function() { 5 | it('should not affect tests outside of the dataDriven function', function() { 6 | 7 | }); 8 | 9 | dataDriven([{ key: 'key1', prop: 'value1' }, { key: 'key2', prop: 'value2' }], function() { 10 | it('should run the data driven function with {key}', function(ctx) { 11 | (ctx.key == 'key1' || ctx.key == 'key2').should.be.true 12 | ctx.prop.should.equal('value1') // fail one test 13 | }); 14 | 15 | it('should allow async data driven testing with {key} value {prop}', function(ctx, done) { 16 | done(); 17 | }); 18 | 19 | it('should allow timeouts for async data driven testing with {key}', function(ctx, done) { 20 | }); 21 | }); 22 | 23 | dataDriven([{ foo: { bar: 'deep value', foo1: { bar1: 'deeper value' } }}], function() { 24 | it('should handle {foo.bar} and {foo.foo1.bar1} lookups', function (ctx) { 25 | }); 26 | }); 27 | 28 | describe('this object:', function() { 29 | var sharedData = 'dummy data'; 30 | 31 | beforeEach(function() { 32 | this.sharedData = sharedData; 33 | }); 34 | 35 | dataDriven([{}], function() { 36 | before(function(ctx) { 37 | this.syncData = sharedData; 38 | }); 39 | 40 | it('should pass appropriate this object to sync test function', function(ctx) { 41 | this.sharedData.should.equal(sharedData); 42 | this.syncData.should.equal(sharedData); 43 | }); 44 | 45 | before(function(ctx, done) { 46 | this.asyncData = sharedData; 47 | done(); 48 | }); 49 | 50 | it('should pass appropriate this object to async test function', function(ctx, done) { 51 | this.sharedData.should.equal(sharedData); 52 | this.asyncData.should.equal(sharedData); 53 | done(); 54 | }); 55 | }) 56 | }) 57 | 58 | dataDriven([{key: 'promises'}], function() { 59 | 60 | it('should allow returning successful {key}', function(ctx) { 61 | return { 62 | then: function(success, reject) { success(); } 63 | }; 64 | }) 65 | 66 | it('should allow returning rejected {key}', function(ctx) { 67 | return { 68 | then: function(success, reject) { reject(); } 69 | }; 70 | }) 71 | 72 | it('should allow timing out {key}', function(ctx) { 73 | return { 74 | then: function(success, reject) { } 75 | }; 76 | }) 77 | }) 78 | }) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | data-driven 2 | =========== 3 | 4 | data-driven is an extension to the [mocha](http://mochajs.org/) JavaScript testing framework that makes data driven (also known as _parameterized_ or _table driven_) testing easier. 5 | 6 | data-driven allows tests to be wrapped in a block that will run each test within that block for every item in the given data set. 7 | 8 | ### Specifying test data 9 | 10 | Test data is simply specified as an array of objects. The objects can contain any data you like that is relevant to your particular tests. 11 | 12 | The test functions within that block work exactly as they do with regular mocha, with the exception that they get passed an additional first parameter with the particular test data to execute the test with. 13 | 14 | ### Describing tests 15 | 16 | Test descriptions can have placeholders wrapped in ```{}```. Any placeholders that match items in the test object will be replaced with the value for that test run e.g. if your test data looked like ```[{name: 'bob'},{name: 'alice'}]```, and you had a description like ```it('should work with a person called {name}', ...```, then you would end up with a test named *should work with a person called bob*, and another test named *should work with a person called alice* when the tests are actually executed. 17 | 18 | ## Example 19 | 20 | ```javascript 21 | var data_driven = require('data-driven') 22 | 23 | describe('Array', function() { 24 | describe('#indexOf()', function(){ 25 | data_driven([{value: 0},{value: 5},{value: -2}], function() { 26 | it('should return -1 when the value is not present when searching for {value}', function(ctx){ 27 | assert.equal(-1, [1,2,3].indexOf(ctx.value)); 28 | }) 29 | }) 30 | }) 31 | }) 32 | 33 | ``` 34 | 35 | You aren't limited to single tests within the data\_driven block. You can have as many as you like, and can also have a mixture of tests inside and outside of the data\_driven block within a single test case, and can also have multiple data\_driven blocks within your code too. 36 | 37 | ### Asynchronous tests 38 | 39 | Asynchronous testing can also be performed as with regular mocha 40 | 41 | ```javascript 42 | 43 | describe('some async function', function() { 44 | data_driven([{name: 'negative', data: -1, expected: -10},{name: 'positive', data: 1, expected: 10}], function() { 45 | it('should do something ascynchronous with {name} numbers', function(ctx,done) { 46 | some_async_call(ctx.data, function(result) { 47 | assert.equal(ctx.expected, result) 48 | done() 49 | }) 50 | }) 51 | }) 52 | }) 53 | 54 | ``` 55 | 56 | ## Installation 57 | 58 | To start using data-driven, simply install it as a dev-dependency (`npm install --save-dev data-driven`). 59 | 60 | From the creators of [uncademy.io](http://uncademy.io). Interactive courses for software developers. 61 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * data-driven 3 | * Copyright(c) 2013 Fluent Software Solutions Ltd 4 | * MIT Licensed 5 | */ 6 | 7 | (function (global) { 8 | /** 9 | * Replaces all tokens in a given test title. This only supports Object lookups using dot notation. 10 | * 11 | * @param {Array} result Results from the RegExp.prototype.exec invocation 12 | * @param {Object} testData The test data set from which we can extract values 13 | * @param {String} title The original test title 14 | * @return {String} The test title with all tokens replaced with their respective values 15 | */ 16 | function replaceTitleTokens(result, testData, title) { 17 | var root = testData; 18 | 19 | // Extract the value 20 | result[1].split('.').forEach(function (key) { 21 | root = root[key]; 22 | }); 23 | 24 | return title.replace(result[0], root); 25 | } 26 | 27 | function dataDriven(data, fn) { 28 | var mochaIt = it; 29 | var mochaBefore = before; 30 | // Regex used to find tokens, e.g. {foo.bar}, {foo} 31 | var re = /{([0-9a-zA-Z_$\._]+)}/g; 32 | 33 | data.forEach(function(testData) { 34 | try { 35 | it = function(title, f) { 36 | var result; 37 | 38 | for (var key in testData) { 39 | if (testData.hasOwnProperty(key)) { 40 | while (result = re.exec(title)) { 41 | title = replaceTitleTokens(result, testData, title); 42 | } 43 | } 44 | } 45 | 46 | if (f !== undefined) { 47 | var testFn = f.length < 2 ? 48 | function() { 49 | return f.call(this,testData) 50 | } : 51 | function(done) { 52 | return f.call(this,testData,done) 53 | } 54 | } 55 | 56 | mochaIt(title, testFn); 57 | }; 58 | 59 | before = function(f) { 60 | var testFn = f.length < 2 ? 61 | function() { 62 | return f.call(this,testData) 63 | } : 64 | function(done) { 65 | return f.call(this,testData,done) 66 | } 67 | 68 | mochaBefore(testFn); 69 | }; 70 | 71 | fn(); 72 | } finally { 73 | it = mochaIt; 74 | before = mochaBefore; 75 | } 76 | }) 77 | } 78 | 79 | if (typeof module !== 'undefined' && module.exports) { 80 | module.exports = dataDriven; 81 | } else if (typeof define === 'function' && define.amd) { 82 | define('data-driven', [], function () { 83 | return dataDriven; 84 | }); 85 | } else { 86 | global.dataDriven = dataDriven; 87 | } 88 | })(this); 89 | --------------------------------------------------------------------------------