How to delay server from running before Tests run - node.js

I've been trying to get around writing functional tests for my services using jest.
The action in the tests gets resolved but the test never passed because it keeps complaining that the server is already in use.
startServer()
describe("api tests", () => {
it("createUser: should create user successfully", async () => {
const user = await createUserService(userCredentials)
expect(user.email).toBe("test2#test.com")
}, 12000)
})
If i check my database, the user actually gets created in the process but for some reasons the tests does't pass and it return an error saying port address in use.
I have also tried to call startServer() in jest's beforeAll(() => {}). It still returns the same behaviour.

Related

Jest extremely slow on failed tests

I have a jest test file that looks like this:
import Client from "socket.io-client"
describe("my awesome project", () => {
let clientSocket;
beforeAll((done) => {
clientSocket = new Client(`http://localhost:3000`);
clientSocket.on("connect", done);
});
afterAll(() => {
clientSocket.close();
});
it("should work", (done) => {
clientSocket.on("redacted", (message) => {
expect(2 + 2).toBe(56);
//expect(message === "foobar").toEqual(true);
done();
});
clientSocket.emit("redacted", "world");
});
});
This is a POC and this currently the entire implementation.
The jest.config looks like this:
export default {
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A preset that is used as a base for Jest's configuration
preset: "ts-jest",
};
Which is just the file the --intit command generated.
The core of my problem is that any expect I use that results in a failed test, no matter how trivial takes an absurd amount of time to complete. I accidentally left it running as above overnight and it eventually completed in 14 hours.
But with a passing test Jest is absolutely fine and completes rapidly.expect(2 + 2).toBe(4); for example runs perfectly. On the failed tests I see the data come back from the socket as expected in the time expected. Its only when the expect is hit that it stalls out. So I don't believe the problem is in the socket setup or some sort of communication problem.
I've tried the config based solutions I've found to no effect - eg Jest - Simple tests are slow
This is being run on my local windows machine from a terminal I'm fully starting and stopping for each test on my IDE.
OK so now I see the problem, I needed a try catch.
Can't quite believe none of the examples or docs I looked at as much as hinted this is necessary to handle something so basic.
test("should work", (done) => {
clientSocket.on("redacted", (message: string) => {
try {
expect(2 + 2).toBe(56);
//expect(message === "foobar").toEqual(true);
done();
} catch (err) {
done(err)
}
});
clientSocket.emit("redacted", "world");
});

Selenium Test Case not getting executed with Jest

I am working on setting up an automation test suite for an application using selenium and jest and it turns out that the console never reaches the inner body of the minimalist test case written below.
describe('When a user Opens Launchpad', () => {
test('It should be able to Navigate to Tasks Application without errors', async () => {
driver.get('http://localhost:4004/fiori.html').then(function () {
const temp = driver.findElement(By.xpath("li[#id='__tile11']"));
temp.then(function (element){
element.getAttribute("innerHTML");
expect(element.getText()).toBe("VT Dashboard");
})
});
}, 200000);
});
I looked online and tried multiple fixes like putting the driver.get() method above all these functions, making the test cases synchronous and using getText() instead of getAttribute() but none of them worked.
I either get an element not found error (The element actually exists when I check it on the chromium browser) or the test case executes successfully without reaching the expect statement in debug mode.
Bottomline is that driver.findElement() returns a promise instead of an element and would be great if I could get an element instead of promise.
Any help or correction here would be greatly appreciated.
If the function is async you should return the promise chain from that function or just use await keyword. So try following:
test('It should be able to Navigate to Tasks Application without errors', async () => {
await driver.get('http://localhost:4004/fiori.html');
const temp = await driver.findElement(By.xpath("li[#id='__tile11']"));
element.getAttribute("innerHTML");
expect(element.getText()).toBe("VT Dashboard");
}, 200000);
or
test('It should be able to Navigate to Tasks Application without errors', async () => {
return driver.get('http://localhost:4004/fiori.html').then(function () {
const temp = driver.findElement(By.xpath("li[#id='__tile11']"));
temp.then(function (element){
element.getAttribute("innerHTML");
expect(element.getText()).toBe("VT Dashboard");
})
});
}, 200000);

NodeJS and ExpressJS app times out with TAP

I'm trying to test an ExpressJS (4.17.8) and NodeJS (16.3) powered server (app) with tap, and later with supertest. First I'm testing the instantiation of the server, and later its routes.
For this, my app is wrapped in a Connector Class that:
has an ExpressJS server (app)
connects to an external system
registers endpoints
has a method startup that calls app.listen
So I have a test file like this:
import test, { Test } from "tape-promise/tape";
test("connects to X", async (t: Test) => {
connector = new Connector();
await connector.ConnectToExternalSystem(); // connects to external system
await connector.registerEndpoints(); // e.g., sets to the Express app: app[get](/endpoint)...
await connector.listen(); // gets stuck?
t.ok(connector);
t.end();
My problem is that for every test I perform, tap seems to get stuck (happens with Jest as well) in connector.listen() - leading for the test to timeout.
My project and tests are written in Typescript 4.3.5. I am using the following npm script to run the tests:
"test": "tap --ts --jobs=1 --node-arg=--max-old-space-size=4096 --timeout=15 --branches=45 --functions=70 --lines=75 --statements=75 \"src/test/{unit,integration}/\"",
Is there anything I'm doing wrong? Appreciate your advice on this.
Thanks.
Depends on what the implementation of Connector really looks like. Assuming that the .listen() method of it calls express' listen under the hood the issue might be that you are not handling the success callback or that it's not wired up properly to the returned promise via the resolve callback of the promise.
So something like this could work (rough pseudo code, not tested):
class Connector {
listen(): Promise<void> {
return new Promise((resolve, reject) => {
this.expressApp.listen((err: Error) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}
What the above does is ensures that the returned promise resolves once the callback has been invoked (or rejects if the callback was passed in an error which is the standard NodeJS error handling style)

Right way to terminate testing mongodb server

We are using mongodb-prebuilt package for perform unit/integration testing (CI/CD) for the developed software, which performs interaction with mongodb database.
Common use case is following: launch testing mongodb server with prebuild package, perform test suite execution and then terminate server. First and second items are done easily, but third one causes some problems.
If mongodb-prebuilt-started server had not been terminated, test runner will hang forever. Instead, if try to terminate server by testConnection.command({ shutdown: 1 }) command, then unhandledRejection fires, which refers on closed connection - which of course had been forcefully closed by stopping the server.
Which is right way to dispose mongodb-prebuilt at afterAll test section? Test engine is jest, but it does not matter strongly.
Example code here:
import { MongodHelper } from 'mongodb-prebuilt';
import { MongoClient } from 'mongodb';
describe('Test suite', () => {
let testConnection;
beforeAll(async () => {
const mongodHelper = new MongodHelper(['--port', "27018"]);
await mongodHelper.run();
testConnection = await MongoClient.connect(`mongodb://localhost:27018/admin`);
});
afterAll(async () => {
await testConnection.dropDatabase();
await testConnection.command({ shutdown: 1 });
await testConnection.close();
testConnection = null;
});
/* Here follows some test suite that uses testConnection */
}
There was some attempts to solve problem:
1) Don't await testConnection.command({ shutdown: 1 })-generated promise, and instantly initiate client connection closing - it works on some machines, but most likely depends on execution speed, so works unstable.
2) Because of client connection termination at the end of the tests does not matter strongly - it's possible to setup process.on('unhandledRejection', ...) handler in afterAll section and just mute exception - works well, but seems to be ideologically incorrect
So, maybe is there stereotypical solution for original task?
IMPORTANT NOTE: suggesting use some mock packages instead live mongodb is not appropriate at all, since developed software is specific adapter for mongodb and should respect all aspects of live database, including timings, admin commands and so on
Most convenient solution for original problem is using mongo-unit npm package, which despite the name, provides API for automated integrated testing with real mongodb prebuild server, including launch and stopping it: https://github.com/mikhail-angelov/mongo-unit/
Gauge test suite with mongo-unit looks like following:
import 'regenerator-runtime/runtime';
import mongoUnit from 'mongo-unit';
import { MongoClient } from 'mongodb';
describe('MongoDB-based test suite, () => {
let testConnection;
beforeAll(async () => {
const connectionUrl = await mongoUnit.start({ dbName: 'admin' });
testConnection = await MongoClient.connect(connectionUrl);
});
afterAll(async () => {
await testConnection.dropDatabase();
await testConnection.close(true);
await mongoUnit.stop();
testConnection = null;
});
/* Test suite goes here */
});
Package also allows to select initial database at start, and perform clear mongodb server process stopping at end of test suite.

Mocha ignores some tests although they should be run

I'm correctly working on refactoring my clone of the express-decorator NPM package. This includes refactoring the unit tests that were previously done using AVA. I decided to rewrite them using Mocha and Chai because I like the way they define tests a lot more.
So, what is my issue? Take a look at this code (I broke it down to illustrate the problem):
test('express', (t) => {
#web.basePath('/test')
class Test {
#web.get('/foo/:id')
foo(request, response) {
/* The test in question. */
t.is(parseInt(request.params.id), 5);
response.send();
}
}
let app = express();
let controller = new Test();
web.register(app, controller);
t.plan(1);
return supertest(app)
.get('/test/foo/5')
.expect(200);
});
This code works.
Here's (basically) the same code, now using Mocha and Chai and multiple tests:
describe('The test express server', () => {
#web.basePath('/test')
class Test {
#web.get('/foo/:id')
foo(request, response) {
/* The test in question. */
it('should pass TEST #1',
() => expect(toInteger(request.params.id)).to.equal(5))
response.send()
}
}
const app = express()
const controller = new Test()
web.register(app, controller)
it('should pass TEST #2', (done) => {
return chai.request(app)
.get('/test/foo/5')
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
done()
})
})
})
The problem is that the TEST #1 is ignored by Mocha although that part of the code is run during the tests. I tried to console.log something there and it appeared in the Mocha log where I expected it to appear.
So how do I get that test to work? My idea would be to somehow pass down the context (the test suite) to the it function, but that's not possible with Mocha, or is it?
It looks like you are moving from tape or some similar test runner to Mocha. You're going to need to significantly change your approach because Mocha works significantly differently.
tape and similar runners don't need to know ahead of time what tests exist in the suite. They discover tests as they go along executing your test code, and a test can contain another test. Mocha on the other hand requires that the entire suite be discoverable before running any test.* It needs to know each and every test that will exist in your suite. It has some disadvantages in that you cannot add tests while the Mocha is running the test. You could not have a before hook for instance do a query from a database and from that create tests. You'd have instead to perform the query before the suite has started. However, this way of doing things also has some advantages. You can use the --grep option to select only a subset of tests and Mocha will do it without any trouble. You can also use it.only to select a single test without trouble. Last I checked, tape and its siblings have trouble doing this.
So the reason your Mocha code is not working is because you are creating a test after Mocha has started running the tests. Mocha won't right out crash on you but when you do this, the behavior you get is undefined. I've seen cases where Mocha would ignore the new test, and I've seen cases where it executed it in an unexpected order.
If this were my test what I'd do is:
Remove the call to it from foo.
Modify foo to simply record the request parameters I care about on the controller instance.
foo(request, response) {
// Remember to initialize this.requests in the constructor...
this.requests.push(request);
response.send()
}
Have the test it("should pass test #2" check the requests recorded on the controller:
it('should pass TEST #2', (done) => {
return chai.request(app)
.get('/test/foo/5')
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(controler.requests).to.have.lengthOf(1);
// etc...
done()
})
})
And would use a beforeEach hook to reset the controller between tests so that tests are isolated.

Resources