├── .gitignore ├── .npmrc ├── renovate.json ├── __snapshots__ └── order-of-tests-spec.js.snap-shot ├── spec ├── nested │ └── deep-nested-spec.js ├── tricky-spec.js ├── fixed-spec.js └── tricky-describe-spec.js ├── .travis.yml ├── bin └── rocha.js ├── src ├── order-cache.js ├── order-of-tests.js └── order-of-tests-spec.js ├── index.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .DS_Store 4 | .rocha.json 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | save-exact=true 3 | engine-strict=true 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "automerge": true, 4 | "major": { 5 | "automerge": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /__snapshots__/order-of-tests-spec.js.snap-shot: -------------------------------------------------------------------------------- 1 | exports['does not shuffle empty 1'] = {} 2 | 3 | exports['does not shuffle empty suites 1'] = { 4 | "suites": [] 5 | } 6 | 7 | -------------------------------------------------------------------------------- /spec/nested/deep-nested-spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it */ 2 | describe('nested spec', () => { 3 | it('works', () => { 4 | console.assert(true, 'all good') 5 | }) 6 | }) 7 | -------------------------------------------------------------------------------- /spec/tricky-spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | describe('tricky example', function () { 3 | var foo 4 | it('runs test 1', function () { 5 | foo = 42 6 | console.log('polluted the environment') 7 | }) 8 | it('runs test 2', function () {}) 9 | it('runs test 3', function () { 10 | console.assert(foo === 42, 'foo is 42', foo) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: true 8 | node_js: 9 | - '8' 10 | before_script: 11 | - npm prune 12 | script: 13 | - npm run lint 14 | - npm run mocha 15 | after_success: 16 | - npm run semantic-release 17 | branches: 18 | except: 19 | - "/^v\\d+\\.\\d+\\.\\d+$/" 20 | -------------------------------------------------------------------------------- /spec/fixed-spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it */ 2 | describe('fixed example', function () { 3 | var foo 4 | beforeEach(function () { 5 | foo = undefined 6 | }) 7 | it('runs test 1', function () { 8 | foo = 42 9 | console.log('polluted the environment') 10 | }) 11 | it('runs test 2', function () {}) 12 | it('runs test 3', function () { 13 | console.assert(foo !== 42, 'foo is not 42', foo) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /spec/tricky-describe-spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | describe('tricky describe example', function () { 4 | var foo 5 | describe('in environment 1', function () { 6 | it('runs a test', function () { 7 | foo = 42 8 | console.log('polluted the environment') 9 | }) 10 | }) 11 | describe('in environment 2', function () { 12 | it('runs a test', function () { 13 | console.assert(foo === 42, 'foo is 42', foo) 14 | }) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /bin/rocha.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | const join = require('path').join 6 | 7 | const help = [ 8 | 'USE: rocha ', 9 | ' rocha *-spec.js', 10 | ' rocha spec/my-test-file.js' 11 | ].join('\n') 12 | 13 | require('simple-bin-help')({ 14 | minArguments: 3, 15 | packagePath: join(__dirname, '..', 'package.json'), 16 | help: help 17 | }) 18 | 19 | // console.log(process.argv) 20 | 21 | const spec = process.argv.slice(2) 22 | const rocha = require('..') 23 | rocha({ spec: spec }) 24 | -------------------------------------------------------------------------------- /src/order-cache.js: -------------------------------------------------------------------------------- 1 | const log = require('debug')('rocha') 2 | const la = require('lazy-ass') 3 | const is = require('check-more-types') 4 | const join = require('path').join 5 | const filename = join(process.cwd(), '.rocha.json') 6 | const exists = require('fs').existsSync 7 | const rm = require('fs').unlinkSync 8 | const read = require('fs').readFileSync 9 | 10 | function saveOrder (ordered) { 11 | la(is.array(ordered), 'expected a list of suites', ordered) 12 | 13 | const json = JSON.stringify(ordered, null, 2) 14 | const save = require('fs').writeFileSync 15 | save(filename, json) 16 | log('saved order to file', filename) 17 | return filename 18 | } 19 | 20 | function clearSavedOrder () { 21 | if (exists(filename)) { 22 | rm(filename) 23 | log('tests have passed, deleted the current random order', filename) 24 | } 25 | return filename 26 | } 27 | 28 | function loadOrder () { 29 | if (!exists(filename)) { 30 | return 31 | } 32 | const json = read(filename) 33 | const order = JSON.parse(json) 34 | log('loaded order from', filename) 35 | return order 36 | } 37 | 38 | module.exports = { 39 | save: saveOrder, 40 | clear: clearSavedOrder, 41 | load: loadOrder, 42 | filename: function () { 43 | return filename 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Mocha = require('mocha') 2 | const log = require('debug')('rocha') 3 | // verbose output during e2e tests 4 | const e2e = require('debug')('rocha:e2e') 5 | const la = require('lazy-ass') 6 | const is = require('check-more-types') 7 | const chalk = require('chalk') 8 | 9 | const order = require('./src/order-of-tests') 10 | const cache = require('./src/order-cache') 11 | la(is.object(cache), 'missing test order object') 12 | 13 | function rocha (options) { 14 | options = options || {} 15 | 16 | const mocha = options.mocha || new Mocha() 17 | 18 | log('starting rocha with options') 19 | log(JSON.stringify(options, null, 2)) 20 | 21 | var specFilenames = options.spec 22 | if (!specFilenames) { 23 | console.error('Missing spec file pattern') 24 | process.exit(-1) 25 | } 26 | 27 | if (typeof specFilenames === 'string') { 28 | specFilenames = [specFilenames] 29 | } 30 | 31 | specFilenames.forEach(mocha.addFile.bind(mocha)) 32 | 33 | mocha.suite.beforeAll(function () { 34 | const cachedOrder = cache.load() 35 | if (cachedOrder) { 36 | log('reordering specs like last time') 37 | order.set(mocha.suite, cachedOrder) 38 | } else { 39 | const randomOrder = order.shuffle(mocha.suite) 40 | const names = order.collect(randomOrder) 41 | e2e('shuffled names:') 42 | e2e('%j', names) 43 | } 44 | 45 | // the order might be out of date if any tests 46 | // were added or deleted, thus 47 | // always collect the order 48 | const testNames = order.collect(mocha.suite) 49 | cache.save(testNames) 50 | }) 51 | 52 | mocha.run(function (failures) { 53 | process.on('exit', function () { 54 | if (failures === 0) { 55 | cache.clear() 56 | } else { 57 | const filename = cache.filename() 58 | la(is.unemptyString(filename), 'missing save filename') 59 | console.error('Failed tests order saved in', chalk.yellow(filename)) 60 | console.error('If you run Rocha again, the same failed test order will be used') 61 | } 62 | process.exit(failures) 63 | }) 64 | }) 65 | } 66 | 67 | module.exports = rocha 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rocha", 3 | "description": "Runs Mocha unit tests but randomizes their order", 4 | "main": "index.js", 5 | "version": "0.0.0-development", 6 | "bin": { 7 | "rocha": "bin/rocha.js" 8 | }, 9 | "engines": { 10 | "node": ">=4.0.0" 11 | }, 12 | "dependencies": { 13 | "chalk": "2.4.2", 14 | "check-more-types": "2.24.0", 15 | "debug": "3.2.6", 16 | "lazy-ass": "1.6.0", 17 | "lodash": "4.17.11", 18 | "mocha": "5.2.0", 19 | "ramda": "0.26.1", 20 | "ramda-fantasy": "0.8.0", 21 | "simple-bin-help": "1.7.1" 22 | }, 23 | "files": [ 24 | "bin", 25 | "index.js", 26 | "src/*.js", 27 | "!src/*-spec.js" 28 | ], 29 | "scripts": { 30 | "test": "npm run mocha", 31 | "pretest": "npm run lint", 32 | "random": "node bin/rocha.js spec/tricky-spec.js spec/tricky-describe-spec.js", 33 | "mocha": "mocha src/*-spec.js spec/**/*-spec.js spec/fixed-spec.js", 34 | "lint": "standard --verbose --fix bin/*.js *.js src/*.js spec/*.js", 35 | "commit": "commit-wizard", 36 | "issues": "git-issues", 37 | "semantic-release": "semantic-release", 38 | "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/bahmutov/rocha.git" 43 | }, 44 | "preferGlobal": true, 45 | "keywords": [ 46 | "mocha", 47 | "cli", 48 | "unit", 49 | "test", 50 | "testing", 51 | "random", 52 | "randomize", 53 | "shuffle" 54 | ], 55 | "author": "Gleb Bahmutov ", 56 | "license": "MIT", 57 | "bugs": { 58 | "url": "https://github.com/bahmutov/rocha/issues" 59 | }, 60 | "homepage": "https://github.com/bahmutov/rocha#readme", 61 | "devDependencies": { 62 | "git-issues": "1.3.1", 63 | "pre-git": "3.17.1", 64 | "semantic-release": "15.13.24", 65 | "simple-commit-message": "4.0.13", 66 | "snap-shot": "2.17.0", 67 | "standard": "14.3.1" 68 | }, 69 | "config": { 70 | "pre-git": { 71 | "commit-msg": "simple", 72 | "pre-commit": [ 73 | "npm test" 74 | ], 75 | "pre-push": [ 76 | "npm run size" 77 | ], 78 | "post-commit": [], 79 | "post-merge": [] 80 | } 81 | }, 82 | "release": { 83 | "analyzeCommits": "simple-commit-message" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/order-of-tests.js: -------------------------------------------------------------------------------- 1 | const log = require('debug')('rocha') 2 | const la = require('lazy-ass') 3 | const is = require('check-more-types') 4 | const _ = require('lodash') 5 | const {Maybe} = require('ramda-fantasy') 6 | const {Just, Nothing} = Maybe 7 | const R = require('ramda') 8 | const {has, pathSatisfies, lt, tap, allPass} = R 9 | 10 | const positive = lt(0) 11 | const isObject = R.is(Object) 12 | 13 | const isValidSuite = allPass([ 14 | isObject, 15 | has('suites'), 16 | pathSatisfies(positive, ['suites', 'length']) 17 | ]) 18 | 19 | function maybeSuites (suite) { 20 | return isValidSuite(suite) ? Just(suite) : Nothing() 21 | } 22 | 23 | const logShuffle = (s) => 24 | log('shuffling %d describe blocks in "%s"', 25 | s.suites.length, s.title) 26 | 27 | const suitesLens = R.lensProp('suites') 28 | const shuffleSuites = R.over(suitesLens, _.shuffle) 29 | const shuffleNestedSuites = R.over(suitesLens, R.map(shuffleDescribes)) 30 | 31 | function shuffleTests (suite) { 32 | if (suite && Array.isArray(suite.tests) && suite.tests.length) { 33 | log('shuffling %d unit tests in "%s"', 34 | suite.tests.length, suite.title) 35 | const shuffled = _.shuffle(suite.tests) 36 | if (R.equals(shuffled, suite.tests)) { 37 | log('need to shuffle %d tests again in %s', 38 | suite.tests.length, suite.title) 39 | } 40 | suite.tests = shuffled 41 | } 42 | if (Array.isArray(suite.suites)) { 43 | suite.suites.forEach(shuffleTests) 44 | } 45 | return suite 46 | } 47 | 48 | function shuffleDescribes (suite) { 49 | return maybeSuites(suite) 50 | .map(tap(logShuffle)) 51 | .map(shuffleSuites) 52 | .map(shuffleNestedSuites) 53 | .getOrElse(suite) 54 | } 55 | 56 | function shuffle (suite) { 57 | return Maybe.toMaybe(suite) 58 | .map(shuffleDescribes) 59 | .map(shuffleTests) 60 | .getOrElse(suite) 61 | } 62 | 63 | function collectSuite (suite, collected) { 64 | collected.push({ 65 | title: suite.fullTitle(), 66 | tests: suite.tests.map(t => t.title) 67 | }) 68 | suite.suites.forEach(s => collectSuite(s, collected)) 69 | } 70 | 71 | function collectTestOrder (rootSuite) { 72 | const suites = [] 73 | collectSuite(rootSuite, suites) 74 | return suites 75 | } 76 | 77 | // reorders given tests according to the given list of titles 78 | // any titles not found will be ignored 79 | // tests without a title will be added at the end 80 | function setTestOrder (suite, tests, titles) { 81 | la(is.array(tests), 'invalid tests', tests) 82 | la(is.array(titles), 'invalid titles', titles) 83 | 84 | const orderedTests = [] 85 | titles.forEach(title => { 86 | const test = _.find(tests, { title: title }) 87 | if (!test) { 88 | log('cannot find test under title', title, 'skipping') 89 | return 90 | } 91 | la(test, 'could not find test with title', title, 92 | 'among', tests, 'in', suite.fullTitle()) 93 | orderedTests.push(test) 94 | }) 95 | const newTests = _.difference(tests, orderedTests) 96 | suite.tests = orderedTests.concat(newTests) 97 | } 98 | 99 | // recursively goes through the tree of suites 100 | function setOrder (suite, order) { 101 | const foundInfo = _.find(order, { title: suite.fullTitle() }) 102 | if (foundInfo) { 103 | log('restoring order to', foundInfo.title) 104 | // need to compare number of tests, etc 105 | setTestOrder(suite, suite.tests, foundInfo.tests) 106 | } 107 | suite.suites.forEach(s => setOrder(s, order)) 108 | } 109 | 110 | module.exports = { 111 | shuffle, 112 | set: setOrder, 113 | collect: collectTestOrder 114 | } 115 | -------------------------------------------------------------------------------- /src/order-of-tests-spec.js: -------------------------------------------------------------------------------- 1 | const la = require('lazy-ass') 2 | const is = require('check-more-types') 3 | const _ = require('lodash') 4 | const {set, shuffle} = require('./order-of-tests') 5 | const snapshot = require('snap-shot') 6 | const R = require('ramda') 7 | 8 | /* global describe, it */ 9 | describe('shuffle', () => { 10 | it('is a function', () => { 11 | la(is.fn(shuffle), shuffle) 12 | }) 13 | 14 | it('does not shuffle undefined', () => { 15 | la(_.isUndefined(shuffle())) 16 | }) 17 | 18 | it('does not shuffle empty', () => { 19 | la(snapshot(shuffle({}))) 20 | }) 21 | 22 | it('does not shuffle empty suites', () => { 23 | const s = { 24 | suites: [] 25 | } 26 | la(snapshot(shuffle(s))) 27 | }) 28 | 29 | it('returns different object', () => { 30 | const s = { 31 | suites: [1, 2, 3] 32 | } 33 | const shuffled = shuffle(s) 34 | la(shuffled !== s, 'returns new object') 35 | }) 36 | 37 | it('returns shuffled suites', () => { 38 | const s = { 39 | suites: R.range(1, 100) 40 | } 41 | const shuffled = shuffle(s) 42 | la(!_.isEqual(shuffled.suites, s.suites), 43 | 'shuffled suites are the same', 44 | shuffled.suites, 'initial', s.suites) 45 | }) 46 | 47 | it('shuffles top level names', () => { 48 | const s1 = { 49 | name: 's1', 50 | suites: R.range(1, 10) 51 | } 52 | const s2 = { 53 | name: 's2', 54 | suites: R.range(20, 30) 55 | } 56 | const s3 = { 57 | name: 's3', 58 | suites: R.range(40, 50) 59 | } 60 | const s = { 61 | suites: [s1, s2, s3] 62 | } 63 | const names = ['s1', 's2', 's3'] 64 | const shuffled = shuffle(s) 65 | const shuffledNsames = R.pickAll('name')(shuffled) 66 | la(!_.isEqual(names, shuffledNsames), 67 | 'suites should be shuffled', shuffledNsames) 68 | }) 69 | 70 | it('shuffles nested suites', () => { 71 | const s1 = { 72 | name: 's1', 73 | suites: R.range(1, 10) 74 | } 75 | const s2 = { 76 | name: 's2', 77 | suites: R.range(20, 30) 78 | } 79 | const s3 = { 80 | name: 's3', 81 | suites: R.range(40, 50) 82 | } 83 | const s = { 84 | suites: [s1, s2, s3] 85 | } 86 | const shuffled = shuffle(s) 87 | const shuffledS1 = R.find(R.propEq('name', 's1'))(shuffled.suites) 88 | la(!_.isEqual(shuffledS1.suites, s1.suites), 89 | 'did not shuffle s1', shuffledS1.suites) 90 | }) 91 | }) 92 | 93 | describe('order of tests', function () { 94 | function toTest (name) { 95 | return { title: name } 96 | } 97 | function toTests () { 98 | return _.toArray(arguments).map(toTest) 99 | } 100 | 101 | it('is a function', () => { 102 | la(is.fn(set)) 103 | }) 104 | 105 | it('works for missing suite', () => { 106 | const suite = { 107 | fullTitle: () => 'foo', 108 | suites: [], 109 | tests: ['foo', 'bar'] 110 | } 111 | const order = [] 112 | set(suite, order) 113 | la(_.isEqual(suite.tests, ['foo', 'bar'])) 114 | }) 115 | 116 | it('changes order to specified', () => { 117 | const suite = { 118 | fullTitle: () => 's1', 119 | suites: [], 120 | tests: toTests('foo', 'bar') 121 | } 122 | const order = [{ 123 | title: 's1', 124 | tests: ['bar', 'foo'] 125 | }] 126 | set(suite, order) 127 | la(_.isEqual(suite.tests, toTests('bar', 'foo')), 128 | 'did not change order', suite.tests) 129 | }) 130 | 131 | it('ignores titles that are not found', () => { 132 | const suite = { 133 | fullTitle: () => 's1', 134 | suites: [], 135 | tests: toTests('foo', 'bar') 136 | } 137 | const order = [{ 138 | title: 's1', 139 | tests: ['bar', 'foo', 'baz'] 140 | }] 141 | set(suite, order) 142 | la(_.isEqual(suite.tests, toTests('bar', 'foo')), 143 | 'kept tests not in order', suite.tests) 144 | }) 145 | 146 | it('adds new tests at the end of the list', () => { 147 | const suite = { 148 | fullTitle: () => 's1', 149 | suites: [], 150 | tests: toTests('foo', 'bar') 151 | } 152 | const order = [{ 153 | title: 's1', 154 | tests: ['bar'] 155 | }] 156 | set(suite, order) 157 | la(_.isEqual(suite.tests, toTests('bar', 'foo')), 158 | 'added foo at the end', suite.tests) 159 | }) 160 | }) 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rocha (aka "ROKKA" the Random Mocha) 2 | 3 | > Runs Mocha unit tests but randomizes their order 4 | 5 | [![NPM][rocha-icon] ][rocha-url] 6 | 7 | [![Build status][rocha-ci-image] ][rocha-ci-url] 8 | [![semantic-release][semantic-image] ][semantic-url] 9 | [![manpm](https://img.shields.io/badge/manpm-%E2%9C%93-3399ff.svg)](https://github.com/bahmutov/manpm) 10 | [![standard style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 11 | [![renovate-app badge][renovate-badge]][renovate-app] 12 | 13 | **E2E tests** 14 | 15 | `rocha-test` - [![Build Status](https://travis-ci.org/bahmutov/rocha-test.svg?branch=master)](https://travis-ci.org/bahmutov/rocha-test) 16 | 17 | ## Install and use 18 | 19 | Should be just like [Mocha](https://mochajs.org/) for most cases 20 | 21 | npm install -g rocha 22 | rocha src/*-spec.js 23 | 24 | Open an [issue][issues] if things do not work as expected. 25 | 26 | Because I used some pieces of ES6, and Ubuntu does not play nicely with `--harmony` 27 | flag (which allows [using some ES6 today](https://glebbahmutov.com/blog/using-node-es6-today/)) 28 | this package requires Node >= 4. 29 | 30 | ## Demo screencast 31 | 32 | [![asciicast](https://asciinema.org/a/31549.png)](https://asciinema.org/a/31549) 33 | 34 | In this demo I am showing how Rocha can find ordering problems in the tests (see the example below). 35 | 36 | ## Example 37 | 38 | The tests in [spec/tricky-spec.js](spec/tricky-spec.js) always pass in Mocha, 39 | but only because their execution order is 1 - 2 - 3. 40 | 41 | ```js 42 | describe('example', function () { 43 | var foo 44 | it('runs test 1', function () { 45 | foo = 42 46 | console.log('polluted the environment') 47 | }) 48 | it('runs test 2', function () {}) 49 | it('runs test 3', function () { 50 | console.assert(foo === 42, 'foo is 42', foo) 51 | }) 52 | }) 53 | ``` 54 | 55 | This tests pass under Mocha but this is very unreliable: a tiny code change 56 | can break the tests for no obvious reason. A pain to find the problem too. 57 | 58 | ### Running tests using Mocha 59 | 60 | > mocha spec/tricky-spec.js 61 | example 62 | polluted the environment 63 | ✓ runs test 1 64 | ✓ runs test 2 65 | ✓ runs test 3 66 | 3 passing (8ms) 67 | 68 | ### Running tests using Rocha 69 | 70 | > rocha spec/tricky-spec.js 71 | shuffling 3 unit tests in "example" 72 | example 73 | 1) runs test 3 74 | polluted the environment 75 | ✓ runs test 1 76 | ✓ runs test 2 77 | 2 passing (10ms) 78 | 1 failing 79 | 1) example runs test 3: 80 | AssertionError: foo is 42 undefined 81 | 82 | Rocha takes each suite and shuffles its list of unit tests. Given enough test runs this should 83 | make visible the problems due to shared data, or polluted environment, or even poor understanding of 84 | JavaScript [concurrency](http://glebbahmutov.com/blog/concurrency-can-bite-you-even-in-node/). 85 | 86 | ## Notes 87 | 88 | Not every random order will be 89 | 90 | - so random that it is different from sequential 91 | - enough to flush out every problem 92 | 93 | ## Recreating the failed order 94 | 95 | If the unit tests fail, **the executed order is saved** in JSON file `.rocha.json`. 96 | For the included example `rocha spec/*-spec.js` it will be something like this 97 | 98 | [{ 99 | "title": "fixed example", 100 | "tests": [ 101 | "runs test 1", 102 | "runs test 2", 103 | "runs test 3" 104 | ] 105 | }, { 106 | "title": "tricky example", 107 | "tests": [ 108 | "runs test 1", 109 | "runs test 3", 110 | "runs test 2" 111 | ] 112 | }] 113 | 114 | When you start `rocha` again, it will find this file and will reorder the tests 115 | **in the same order**, recreating the failure again. 116 | 117 | If the tests pass, the `.rocha.json` file is deleted, thus the next run will be random again. 118 | 119 | ## How should we test? 120 | 121 | Each unit test should NOT depend on the order the other tests are running. In the above case, 122 | refactor the test to reset the variable before each unit test, 123 | see [spec/fixed.spec.js](spec/fixed.spec.js) 124 | 125 | ```js 126 | describe('fixed example', function () { 127 | var foo 128 | beforeEach(function () { 129 | foo = undefined 130 | }) 131 | ... 132 | }); 133 | ``` 134 | 135 | Now each unit test starts from the same values (at least in this example). 136 | 137 | ## Options 138 | 139 | **verbose log** - to see diagnostic messages as Rocha runs, set environment variable `DEBUG=rocha` 140 | when running. 141 | 142 | DEBUG=rocha rocha 143 | 144 | During end to end tests, verbose logging is enabled using `DEBUG=rocha:e2e` 145 | 146 | ### Small print 147 | 148 | Author: Gleb Bahmutov © 2015 149 | 150 | * [@bahmutov](https://twitter.com/bahmutov) 151 | * [glebbahmutov.com](https://glebbahmutov.com) 152 | * [blog](https://glebbahmutov.com/blog/) 153 | 154 | License: MIT - do anything with the code, but don't blame me if it does not work. 155 | 156 | Spread the word: tweet, star on github, etc. 157 | 158 | Support: if you find any problems with this module, email / tweet / 159 | [open issue][issues] on Github 160 | 161 | [issues]: https://github.com/bahmutov/rocha/issues 162 | 163 | ## MIT License 164 | 165 | Copyright (c) 2015 Gleb Bahmutov 166 | 167 | Permission is hereby granted, free of charge, to any person 168 | obtaining a copy of this software and associated documentation 169 | files (the "Software"), to deal in the Software without 170 | restriction, including without limitation the rights to use, 171 | copy, modify, merge, publish, distribute, sublicense, and/or sell 172 | copies of the Software, and to permit persons to whom the 173 | Software is furnished to do so, subject to the following 174 | conditions: 175 | 176 | The above copyright notice and this permission notice shall be 177 | included in all copies or substantial portions of the Software. 178 | 179 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 180 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 181 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 182 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 183 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 184 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 185 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 186 | OTHER DEALINGS IN THE SOFTWARE. 187 | 188 | [rocha-icon]: https://nodei.co/npm/rocha.svg?downloads=true 189 | [rocha-url]: https://npmjs.org/package/rocha 190 | [rocha-ci-image]: https://travis-ci.org/bahmutov/rocha.svg?branch=master 191 | [rocha-ci-url]: https://travis-ci.org/bahmutov/rocha 192 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 193 | [semantic-url]: https://github.com/semantic-release/semantic-release 194 | [renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg 195 | [renovate-app]: https://renovateapp.com/ 196 | --------------------------------------------------------------------------------