Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Split mocha API test in multiple files

I'm building some API tests for the product I'm building. One of the test looks like this:

GET FILTERS
  ✓ should be restricted (45ms)
  it should get the filters
    ✓ should return 200
    ✓ should return an object
    ✓ should close db connections
GET USERS COUNT
  ✓ should be restricted
  ✓ should throw error when payload is not correct
  it should get the user count
    ✓ should return 200
    ✓ should return an object
    ✓ should close db connections
GET USERS FILE
  ✓ should be restricted
  ✓ should throw error when no queryId is specified
  it should retrieve the file
    ✓ should return 200
    ✓ should download an excel file
    ✓ should close db connections
UPLOAD PROMOTION IMAGE
  ✓ should throw error when no file is specified
  it should save the file
    ✓ should return 200
    ✓ should have named the file with the title of the promotion
    ✓ should have uploaded the file to S3 (355ms)
    ✓ should close db connections
CREATE PROMOTION
  it should save the promotion
    ✓ should return 200
    ✓ should return a correct response
    ✓ should close db connections
GET PROMOTIONS
  ✓ should be restricted
  it should get the promotions
    ✓ should return 200
    ✓ should be an array of promotions
    ✓ should contain the previously created promotion
UPDATE PROMOTION
  it should update the promotion
    ✓ should return 200
    ✓ should return a correct response
    ✓ should close db connections
PUT PROMOTION IN BIN
  it should put the promotion in the bin
    ✓ should return 200
    ✓ should return a correct response
    ✓ should close db connections
GET ARCHIVED PROMOTIONS
  ✓ should be restricted
  it should get the promotions
    ✓ should return 200
    ✓ should be an array of promotions
    ✓ should be an array of archived promotions
    ✓ should contain the previously archived promotion
DELETE PROMOTION
  it should delete the promotion
    ✓ should return 200
    ✓ should return a correct response
    ✓ should have deleted the file from S3 (563ms)
    ✓ should close db connections

As you can see I've tried to put everything regarding promotions in a single test suite so that I can have a sort of workflow to test what the user will do exactly on the platform.

In this example I create a random generate promotion and then use the id of that promotion to read it, update it, archive it and then finally delete it. Every step is connected and I need to have return values for each suit (i.e: The ID of the inserted promotion, or the filters...)

At this moment my promotions.test.js file is 625 and, since I have not finished yet, I expect it to grow a lot in the next days.

Is there a way to split multiple test suits in different files but in a way that each test/file is able to return, as soon as it finishes, a value that I can pass to the next step ?

EDIT FOR BOUNTY

At the moment I only tried something like this:

describe.only("Gifts Workflow", function() {

var createdGift;
describe("CREATE", function() {
    require("./GIFTS/CREATE.js")().then(function(data) {
        createdGift = data;
    });
});

describe("READ FROM WEB", function() {
    require("./GIFTS/READ FROM WEB.js")(createdGift).then(function(data) {

    });
});
});

Content of "./GIFTS/CREATE.js"

module.exports = function() {
return new Promise(function(resolve, reject) {

    //DO SOME TESTS WITH IT AND DESCRIBE

    after(function() {
        resolve(createdGift);
    });
});

};

The problem is that the test are immediately initialized by mocha and so in the second test suite "READ FROM WEB" the value passed as createdGift is given immediately to the test without waiting for the first test to complete and so undefined is passed.

Jankapunkt's Answer

This is how I tried in my code:

var create = require("./GIFTS/CREATE");
var read = require("./GIFTS/READ FROM WEB");

describe.only("Gifts Workflow", function() {
    create(function(createdGift) {
        read(createdGift);
    });
});

CREATE

module.exports = function(callback) {
        var createdGift;

        //SOME TESTS

        describe("it should insert a gift", function() {

            var result;
            before(function() {
                return request
                    .post(url)
                    .then(function(res) {
                        createdGift = res.body;
                    });
            });

      //SOME OTHER TESTS

        });


        after(function() {
            callback(createdGift);
        });
};

READ FROM WEB

module.exports = function(createdGift) {
    it("should be restricted", function(done) {
        request
            .get(url)
            .query({})
            .end(function(err, res) {
                expect(res.statusCode).to.equal(400);
                done();
            });
    });

    describe("it should read all gifts", function() {
           //SOME TESTS
    });
};

And this is the output

Gifts Workflow
  ✓ should be restricted
  ✓ should not work when incomplete payload is specified
  it should insert a gift
    ✓ should return 200
    ✓ should return an object
    ✓ should have uploaded the image to S3 (598ms)
    ✓ should close db connections

it should read all gifts
  ✓ should return 200
  ✓ should return an array
  ✓ should contain the previously added gift
  ✓ should close db connections


10 passing (3s)

It may seems it's working but as you can see from the tabulation it should read all gifts is not a children of Gifts Workflow but is a child of the root suite.

This is what happens:

  1. Mocha calls the root suite
  2. Mocha finds the Gifts Workflow suite and executes the create() function inside this suite
  3. Since the function is asynchronous Mocha thinks that the Gifts Workflow suite is ended and returns to the root suite
  4. read() is executed
  5. Mocha exit from the root suite and goes to the next tests because ,being asynchronous, it thinks that all the test are finished
  6. Test #3,4,5,... are never called

Can you confirm this is your case alse with more that two tests ?

like image 590
Matteo Cardellini Avatar asked May 07 '17 21:05

Matteo Cardellini


1 Answers

I was dealing with a similar issue and I found at least workaraound. Please comment, if something is not working.

I came up with this, when I found out, that mocha only auto-executes the describe blocks, when they are in module-scope but not inside a function.

It breaks down to the following approach:

  • Wrap all describe blocks inside functions, which are exported by your modules
  • Pass callbacks to the functions which provide the return value
  • Call functions in sequence using callbacks in your test suite

Reproducable Example

Create minimal test setup

index.js
test1.js
test2.js

In your test files you wrap up your tests in exported functions. Note, that I use ES6 for import/export the modules.

test1.js

export const test1_method = function(callback){

    let returnValue; // declared outside the tests

    describe("test 1", function(){


        it ("1. unit", function(){
            assert.isTrue(true);
            // assigned inside test
            returnValue = 42; 
        });

        it ("2. unit", function(){
            assert.isTrue(true);
            callback(returnValue); // called in the last unit
        });

    });
}

As you can see, this function has a simple callback passed and which is called in the very last unit. You might argue, that this is very vague. I agree, but in practice I have never seen a sequence randomness of it-units inside a describe block. Thus you can assume, that the callback will be called after your last unit has run.

test2.js

export const test2_method = function(previousValue){

    describe("test 2", function(){

        it ("runs correctly with a dependency value", function(){
            assert.equal(previousValue, 42);
        })
    })

}

Not much to add here, just takes the input and tests for a specific value.

index.js

import {test1_method} from './test1.js';
import {test2_method} from './test2.js';


test1_method(function(test1Result){
    // run the other tests in the callback
    test2_method(test1Result);
});

Here is, where you glue the tests together. This would be the root of your suite. You call the first method and provide a callback, where you end up with your result to be passed to your test2 method. The result of your first test is surprisingly not undefined and you can easily pass it as parameter to test2.

Output

I20170515-15:09:33.768(2)?   test 1
I20170515-15:09:33.769(2)? 
I20170515-15:09:33.770(2)?     ✓ 1. unit
I20170515-15:09:33.770(2)? 
I20170515-15:09:33.771(2)?     ✓ 2. unit
I20170515-15:09:33.771(2)? 
I20170515-15:09:33.772(2)?   test 2
I20170515-15:09:33.773(2)? 
I20170515-15:09:33.773(2)?     ✓ runs correctly with a dependency value

Advantages

You can control your test-order and split your suite into sub-suites.

You write your tests parameterized, which als makes them reusable, depending on the use case. In my test suite there is for example one function with a describe set of 10 tests, which is applied for all my mongo collections.

Disadvantages

You need to rewrite all your tests into wrapped function, so that no test is auto-executed by mocha but only by your test suite.

Multiple callbacks inside callbacks makes it hard to read and debug.

Summary

In my opinion not an "official" solution but a workaround and starting point from where you can improve your test suit, if no other solution is found.

like image 184
Jankapunkt Avatar answered Sep 19 '22 12:09

Jankapunkt