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.
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:
Can you confirm this is your case alse with more that two tests ?
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With