96

I'm trying to join all the tests from multiple files in one file, something like this:

  describe('Controllers', function() {
    describe('messages.js', function() {
      require('./controllertests/messages').test(options);
    })
    describe('users.js', function() {
      require('./controllertests/users').test(options);
    })
  })

I'm pretty sure this is not the best way to join tests, I'm having some dificulty finding examples of how to do this :s

coiso
  • 7,151
  • 12
  • 44
  • 63
  • 1
    Curious, why do the tests need to be joined together in one file? – thgaskell Nov 06 '14 at 11:12
  • 2
    For sharing local variables and organization – coiso Nov 06 '14 at 11:15
  • It might make more sense if you include the tests into the question. It sounds like you might be leaning towards integration tests (as opposed to unit tests). Generally you shouldn't need to share variables across tests. – thgaskell Nov 06 '14 at 11:34
  • I guess unit tests is not the best term in fact, API tests might be more correct – coiso Nov 06 '14 at 11:38
  • 2
    And the big issue is that I would prefer to have like 20 files than 1 huuuuge file – coiso Nov 06 '14 at 11:39
  • 2
    Also, if you look at how Mocha handles suites with the concept of `.only()` it might be useful to be able to put `describe.only()` to still run a whole directory of tests. That's what brought me here. – CLo Sep 15 '15 at 21:11
  • I agree with @Chris, Mocha gives you other tools to solve this problem. You are inventing your own here. And indeed like Chris says you will have trouble if you would want to run only one or a few test (with mocha --grep, mocha command line) because their require will be only in the top most js. – kroiz Aug 07 '16 at 04:43

8 Answers8

119

If you want to include multiple modules into your describe hierarchy like you are doing in your question, what you are doing is pretty much it, unless you want to write a custom test loader for Mocha. Writing the custom loader would not be easier or make your code clearer than what you already have.

Here's an example of how I would change a few things. The test subdirectory in this example is organized as:

.
└── test
    ├── a
    │   └── a.js
    ├── b
    │   └── b.js
    ├── common.js
    └── top.js

top.js:

function importTest(name, path) {
    describe(name, function () {
        require(path);
    });
}

var common = require("./common");

describe("top", function () {
    beforeEach(function () {
       console.log("running something before each test");
    });
    importTest("a", './a/a');
    importTest("b", './b/b');
    after(function () {
        console.log("after all tests");
    });
});

The importTest function is just to show how it would be possible to handle the repetition of importing multiple modules without having to retype the whole describe(... require... thing every single time. The common module is meant to hold what you need to use in multiple modules of the test suite. I'm not actually using it in top but it could be used there, if needed.

I will note here that the beforeEach will run its code before each and every single test registered with it whether they appear inside the describe in top or they appear in any of the modules imported. With --recursive, the beforeEach code would have to be copied into each module or perhaps you'd have a beforeEach hook in each module that calls a function imported from a common module.

Also, the after hook will run after all tests in the suite. This cannot be replicated with --recursive. If you use --recursive and add the code of after to each module, it will be executed once per module rather than just once for the whole test.

Having all tests appear under a single top heading cannot be replicated by using --recursive. With --recursive each file could have describe("top" but this would create a new top heading for each file.

common.js:

var chai = require("chai");

var options = {
    foo: "foo"
};

exports.options = options;
exports.chai = chai;
exports.assert = chai.assert;

Using a module named common like this is something I've done in some of my test suites to avoid having to require a bunch of stuff over and over and to hold global read-only variables or functions that don't keep state. I prefer not to pollute the global object like in thgaskell's answer because this object is truly global and accessible even in third party libraries your code may be loading. This is not something I find acceptable in my code.

a/a.js:

var common = require("../common");
var options = common.options;
var assert = common.assert;

it("blah a", function () {
    console.log(options.foo);
    assert.isTrue(false);
});

b/b.js:

it("blah b", function () {});
Louis
  • 146,715
  • 28
  • 274
  • 320
  • 4
    While I agree that you should not pollute `global` scope, I use this for the assertion libraries to keep the test files cleaner. It's not like you're overwriting `global.process`. Local variables will override `global` unless other libraries are explicitly calling `global.XYZ` which is unlikely. It only lasts for the duration of the tests. Hasn't hurt me yet, but I'll let you know the moment it bites me in the ass :) – thgaskell Nov 07 '14 at 10:26
  • What is the difference between `importTest` and calling `require('path')()` for example? – CherryNerd Mar 01 '17 at 15:28
  • @CreasolDev The `importTest` function is just a convenience function. The important thing it does is wrap the `require` call in a `describe` block. It is important that the `require` call be wrapped in `describe` otherwise the modules won't be isolated in their own block and any hook set by the imported file will be set on the wrong block. If `importTest` was replaced with a direct call to `require` without a wrapping `describe`, then the modules `a/a` and `b/b` would share hooks. For instance, a `beforeEach` hook set in `b/b` would also run before each test in `a/a`. – Louis Mar 01 '17 at 15:39
  • Do note that *dynamic require* (like what you are doing with `require(path)` where `path` is a variable) is not recommended, because it does not mesh well with tools like webpack that need to be able to statically analyze require calls. Instead it's better to accept a function i.s.o a path and call require inside the function. With arrow functions that's also pretty neat. – Stijn de Witt Apr 10 '17 at 15:49
  • 1
    I would NOT run any logic such as beforeEach in your top level describe. Let each file do its own beforeEach "stuff". You will be coupling your tests to each other and unrelated implementation if you do this. – PositiveGuy Jul 17 '17 at 05:02
  • 1
    I'd also do the wrapping of describes in their respective files, not in the importTest function. Top level describes in each respective file should be describing their test suites purpose anyway – PositiveGuy Jul 17 '17 at 05:05
38

Although this may not be directly linked to the question, the answer I was looking for was:

$ mocha --recursive

Will execute all tests in sub directories of the "test" folder. Neat. Saves having to maintain a list of tests that I want to load and actually just always run everything.

Ian Jamieson
  • 4,376
  • 2
  • 35
  • 55
  • 3
    Best answer! Far simpler than other proposed solutions. – caiosm1005 Jul 28 '16 at 00:25
  • 14
    @caiosm1005 This answer is not actually solving *the problem presented by the OP*. Sure, *if you do not need to do what the OP wants to do*, then you should be using this. However, if you want to wrap each test file in multiple `describe` blocks, `describe` blocks that span files, `--recursive` won't do it. Seeing as it does not solve the OP's problem, I would not call it "best". – Louis Aug 09 '16 at 10:34
  • @louis - I believe you can wrap each separate file in `describe` blocks – Ian Jamieson Nov 02 '16 at 10:36
  • 4
    @IanJamieson The OP is trying to have *multiple files* covered by a *single* `describe` block. Look at the question. The "Controllers" `describe` block should encompass the tests of `./controllertests/messages.js` and `./controllertests/users.js`. Slapping `--recursive` onto a Mocha invocation does not magically create a `describe("Controllers"` block. – Louis Nov 02 '16 at 10:43
  • 3
    @Louis Just trying to help. Sorry if I offended you by trying to magically create `describe` blocks - which I actually learnt to do from Dumbledore himself. – Ian Jamieson Nov 03 '16 at 10:35
  • This is also what I was looking for. Thank you. I am using: mocha --exit --recursive ./*.tests.js – PhillipJacobs Apr 21 '22 at 19:50
16

There's nothing that prevents you from running multiple test files. Generally, each test should not be dependent on the results of another test, so sharing variables isn't something you'd want to do.

Here's an example of how you could organize your test files.

.
├── app.js
└── test
    ├── common.js
    ├── mocha.opts
    │
    ├── controllers
    │   ├── messages-controller.js
    │   └── users-controller.js
    │
    └── models
        ├── messages-model.js
        └── users-model.js

Then inside of your mocha.opts file, make sure to set the --recursive option.

mocha.opts

--ui bdd
--recursive

If there are common modules that you want to include across all files, you can add that to the common.js file. Files at the root of the test directory will run before files in nested directories.

common.js

global.chai = require('chai');
global.assert = chai.assert;
global.expect = chai.expect;
chai.should();
chai.config.includeStack = true;

process.env.NODE_ENV = 'test';

// Include common modules from your application that will be used among multiple test suites.
global.myModule = require('../app/myModule');
Eduardo Russo
  • 4,121
  • 2
  • 22
  • 38
thgaskell
  • 12,772
  • 5
  • 32
  • 38
  • 3
    Would anyone mind adding code for files in the controllers and models directories? It would be great to have a full example. – Gavin Sep 08 '15 at 13:54
  • @Gavin - these will just be test suits so they would contain `describe('mytest', function() { /* ..... etc */ });` – Ian Jamieson May 19 '16 at 18:43
13

I know this is an old post but I wanted to chime in with what has been a good solution to me, very similar to the method proposed by OP.

The project I'm working on is well tested and the tests keep growing. I ended up using require because it is synchronous and therefore makes it a bit easier to compose your tests without too much change in architecture:

// inside test/index.js

describe('V1 ROUTES', () => {
  require('./controllers/claims.test');
  require('./controllers/claimDocuments.test');
  require('./controllers/claimPhotos.test');
  require('./controllers/inspections.test');
  require('./controllers/inspectionPhotos.test');
  require('./controllers/versions.test');
  require('./services/login.v1.test');
});

describe('V2 ROUTES', () => {
  require('./services/login.v2.test');
  require('./services/dec-image.v2.test');
});

describe('V3 ROUTES', () => {
  require('./services/login.v3.test');
  require('./services/getInspectionPhotosv3.test');
  require('./services/getPolicyInfo.v3.test');
});

describe('ACTIONS', () => {
  require('./actions/notifications.test');
});
Mike Fleming
  • 2,593
  • 4
  • 14
  • 24
3

I had a similar problem where I had bunch of tests for classes in the same category and I wanted to group them together to make viewing them in an IDE easier. All of my tests and code were already using ES6 modules - I didn't want to rewrite all of them to use require like I saw in other examples.

I solved it by having my "grouping" describe exported, and then importing it into my test files and programmatically adding them to the imported describe. I ended up creating a helper method to abstract away all of the plumbing.

In someCategory.spec.js

const someCategory= describe("someCategory", () => {});


// Use this just like a regular `describe` to create a child of this scope in another file
export default function describeMember(skillName, testFn) {
  describe(skillName, function configureContext() {
    // Make context a child of `someCategory` context
    function Context() {}
    Context.prototype = someCategory.ctx;
    this.ctx = new Context();
    // Re-parent the suite created by `describe` above (defaults to root scope of file it was created in)
    this.parent.suites.pop();
    someCategory.addSuite(this);
    // Invoke the fn now that we've properly set up the parent/context
    testFn.call(this);
  });
}

In individual tests:

import { default as describeCategoryMember } from './someCategory.spec';

describeCategoryMember('something', () => {
    describe('somethingElse', () => {
        ...
    });

    it('a test', () => {
        ...
    });
})
CyberT33N
  • 78
  • 5
Jon Senchyna
  • 7,867
  • 2
  • 26
  • 46
0

try this to run all the files that end with _test.js in the tests folder (if it doesnt work be sure to check that you are doing it on ./tests folder)

 "scripts": {
  "test": "find ./tests -name '*_test.js' | xargs mocha -R spec"
},
0
// apple.test.js
import internalTests from './internalTests.js'

describe.only('[---- Apple ----]', () => {
    const search = 'german'
    internalTests(search)

    describe('searchVideo', () => {
        it('should search videos with specific params', async() => {
            // ..
        })
    })
}




// internalTests.js
const internalTests = search => {
    describe('[---- Internal DB ----]', () => {
        describe('_validateCfg', () => {
            it('should throw error because params are not valid', async() => {
                // console.log(search)
            })
        })
    })
}

export default internalTests
t33n
  • 171
  • 1
  • 10
-11
describe( 'Running automation test, Please wait for all test to complete!'.red, function () {


    var run = require( './Test.js' );

    for ( var i = 0; i < 2; i++ ) {
        run.badLogin();
        run.loginLimited();
        run.acceptJob();
        run.drivingToJob();
        run.arrivedAtJob();
        run.towingJob();
        run.arrivedDestination();
        run.jobComplete();
        run.restrictionLicensePlate();
        run.newNodeMainMenu();
        run.newNodeMainMenuToDrafts();
        run.draftDelete();
        run.resetAllData();
        run.companyVehicle();
        run.actionsScreenClockInOut();
        run.mainMenuLogout();
        run.loginAdmin();
        run.actionsScreenLogout();
    }
} );
Mike
  • 23
  • 3
    It is best to add a description along with the code so that others can determine whether this is an acceptable answer. – Suever Feb 01 '16 at 15:12
  • 2
    Why the loop? What is in `./Test.js`? Who knows? For the record, I'm currently the top answerer in the [tag:mocha] tag. I know Mocha inside and out but I cannot make sense of this answer. – Louis Feb 01 '16 at 21:42
  • @Louis seems like he wanted to run the tests *n* times using the loop. – Akash Agarwal Dec 18 '18 at 04:04