I finished creating my express server and I'm using Jest + Supertest to test my endpoints and testing all the CRUD operations for my mongoose db. So far I can't even create a new user in the test.
Code:
//server.spec.js
const supertest = require('supertest');
const server = require('./server');
describe('server.js', () => {
describe('GET /', () => {
it('should connect to server responding w/ 200', (done) => {
return supertest(server)
.get('/')
.then(res => {
expect(res.status).toBe(200);
done();
});
});
});
describe('/api/users/', () => {
it('should create a new user', async () => {
return await supertest(server)
.post('/api/users/register')
.send({ username: "jest", email: "jest#jest.com", password: "jestjest" })
.set("Accept", "application/json")
.then(res => {
console.log(res.body)
expect(res.status).toBe(201)
})
})
});
});
Console output:
FAIL src/server.spec.js (8.573 s)
server.js
GET /
√ should connect to server responding w/ 200 (231 ms)
/api/users/
× should create a new user (5005 ms)
● server.js › /api/users/ › should create a new user
: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
17 | it('should create a new user', async () => {
18 | return await supertest(server)
> 19 | .post('/api/users/register')
| ^
20 | .send({ username: "jest", email: "jest#jest.com", password: "jestjest" })
21 | .set("Accept", "application/json")
22 | .then(res => {
at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
at Suite.describe (src/server.spec.js:19:9)
at Suite.Object.<anonymous>.describe (src/server.spec.js:18:5)
at Object.<anonymous> (src/server.spec.js:6:1)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 8.61 s, estimated 9 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.(node:26872) UnhandledPromiseRejectionWarning: Error: Caught error after test environment was torn down
I've attempted different types of solutions but to no success.
## EDIT ##
Thank you to #Estus Flask for providing me the answer.
I forgot to add my database.
Related
I am trying to develop an API and test it with jest + supertest + typeorm. So, if I import my server instance, I see this error:
ReferenceError: You are trying to "import" a file after the Jest environment has been torn down. From test/Test.test.ts.
// src/app.ts
import express from "express";
import ds from "./app-data-source";
ds.initialize().catch((err) => {
console.error("Error during Data Source initialization:", err)
})
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.on("close", () => ds.destroy())
export default app;
// src/server.ts
import app from "./app";
const server = app.listen(3000);
export default server;
// src/app-data-source.ts
import { DataSource } from "typeorm"
const ds = new DataSource({
type: "sqlite",
database: ":memory:",
entities: ["src/entity/*.ts"],
logging: false,
synchronize: true,
})
export default ds;
// test/Test.test.ts
import request from "supertest";
import server from "../src/server";
describe("GET / - a simple api endpoint", () => {
test("Test #1", async () => {
const response = await request(server).get("/");
expect(response.statusCode).toBe(404);
});
afterAll(done => {
server.close(done)
})
});
And as a result I get this error
yarn run v1.22.17
warning ../package.json: No license field
$ NODE_ENV=test jest --runInBand
PASS test/Test.test.ts
GET / - a simple api endpoint
✓ Test #1 (9 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.134 s, estimated 2 s
Ran all test suites.
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down. From test/Test.test.ts.
at tryToRequire (node_modules/src/util/ImportUtils.ts:21:17)
at importOrRequireFile (node_modules/src/util/ImportUtils.ts:35:25)
at async /Users/sergey/Work/*****/node_modules/src/util/DirectoryExportedClassesLoader.ts:57:45
at async Promise.all (index 0)
at async importClassesFromDirectories (node_modules/src/util/DirectoryExportedClassesLoader.ts:63:18)
at async ConnectionMetadataBuilder.buildEntityMetadatas (node_modules/src/connection/ConnectionMetadataBuilder.ts:92:17)
at async DataSource.buildMetadatas (node_modules/src/data-source/DataSource.ts:674:13)
at async DataSource.initialize (node_modules/src/data-source/DataSource.ts:242:13)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
I figured out that this is a problem, probably due to an open typeorm connection, but I don't know how to fix it because I do
// In test file
// ...
afterAll(done => {
server.close(done)
})
// ...
as well as
import ds from "./app-data-source";
ds.initialize() // ...
// ...
app.on("close", () => ds.destroy())
I tried to search for information on Google and did not find anything useful, all the answers do not help me, useFakeTimers not working for me
I did test many functions in my app using jest / mongodb-memory-server and they all works great. But but some reason I cannot run API call test.
Every time I'm trying to run a api call I have a setimout problem and even if I increase it it still does not work. My functions just return a list of users... no more.
FAIL Tests/Controller/auth.test.js (11.991 s)
● Console
console.warn
Using NodeJS below 12.22.0
at Object.<anonymous> (node_modules/mongodb-memory-server-core/src/util/MongoInstance.ts:21:11)
at Object.<anonymous> (node_modules/mongodb-memory-server-core/src/MongoMemoryServer.ts:13:1)
● test updateSchemaField() › get users list
MongooseError: Can't call `openUri()` on an active connection with different connection strings. Make sure you aren't calling `mongoose.connect()` multiple times. See: https://mongoosejs.com/docs/connections.html#multiple_connections
10 | useUnifiedTopology: true,
11 | };
> 12 | await mongoose.connect(uri, mongooseOpts);
| ^
13 | };
14 |
15 | module.exports.closeDatabase = async () => {
at NativeConnection.Object.<anonymous>.Connection.openUri (node_modules/mongoose/lib/connection.js:697:13)
at node_modules/mongoose/lib/index.js:330:10
at node_modules/mongoose/lib/helpers/promiseOrCallback.js:32:5
at promiseOrCallback (node_modules/mongoose/lib/helpers/promiseOrCallback.js:31:10)
at Mongoose.Object.<anonymous>.Mongoose._promiseOrCallback (node_modules/mongoose/lib/index.js:1151:10)
at Mongoose.connect (node_modules/mongoose/lib/index.js:329:20)
at Object.<anonymous>.module.exports.connect (Tests/db.js:12:18)
at Tests/Controller/auth.test.js:5:23
● test updateSchemaField() › get users list
thrown: "Exceeded timeout of 5000 ms for a hook.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
5 | beforeAll(async () => await db.connect());
6 |
> 7 | afterEach(async () => await db.clearDatabase());
| ^
8 |
9 | afterAll(async () => await db.closeDatabase());
10 |
at Object.<anonymous> (Tests/Controller/auth.test.js:7:1)
● Test suite failed to run
thrown: "Exceeded timeout of 5000 ms for a hook.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
7 | afterEach(async () => await db.clearDatabase());
8 |
> 9 | afterAll(async () => await db.closeDatabase());
| ^
10 |
11 | describe("test updateSchemaField()", (done) => {
12 | it("get users list", async () => {
at Object.<anonymous> (Tests/Controller/auth.test.js:9:1)
PASS Tests/Data/schemaCRUD.test.js
● Console
console.warn
Using NodeJS below 12.22.0
at Object.<anonymous> (node_modules/mongodb-memory-server-core/src/util/MongoInstance.ts:21:11)
at Object.<anonymous> (node_modules/mongodb-memory-server-core/src/MongoMemoryServer.ts:13:1)
PASS Tests/Utils/user.test.js
Test Suites: 1 failed, 2 passed, 3 total
Tests: 1 failed, 9 passed, 10 total
Snapshots: 0 total
Time: 12.859 s, estimated 13 s
Ran all test suites matching /.\/Tests/i.
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
The test
const request = require("supertest");
const app = require("../../app");
const db = require("../db");
beforeAll(async () => await db.connect());
afterEach(async () => await db.clearDatabase());
afterAll(async () => await db.closeDatabase());
describe("test updateSchemaField()", () => {
it("get users list", async () => {
const response = await request(app).get("/api/auth/test");
console.log(response);
});
});
The script is trying to connect with multiple URI. You are establishing a database connection in the test file is fine but there is also another database connection call with different URI in app.js .
I am trying to test my node api index endpoint with the code below
index.test.js
const chai = require('chai')
const chaiHttp = require('chai-http')
const server = 'http://localhost:8000'
chai.use(chaiHttp)
describe('set up test', () => {
it('set up test', () => {
expect(1).toEqual(1)
})
})
describe('index route test', () => {
it('index route test', (done) => {
const { res } = chai
.request(server)
.get('/')
.end((res) => {
expect(res.statusCode).toBe(200)
done()
})
console.log(res)
})
})
My fail test for 'index route test' passes yet it's supposed to fail(SOLVED).
The Received response is undefined(which I logged in the console).
My output is as below:
Output
> jest --forceExit || true
FAIL tests/index.test.js
set up test
✓ set up test (6 ms)
index route test
✕ gives welcome message (49 ms)
● index route test › gives welcome message
expect(received).toBe(expected) // Object.is equality
Expected: 200
Received: undefined
18 | .get('/')
19 | .end((res) => {
> 20 | expect(res.statusCode).toBe(200)
| ^
21 | done()
22 | })
23 | console.log(res)
at tests/index.test.js:20:32
at Test.Object.<anonymous>.Request.callback (node_modules/superagent/lib/node/index.js:728:3)
at ClientRequest.<anonymous> (node_modules/superagent/lib/node/index.js:647:10)
console.log
undefined
at Object.<anonymous> (tests/index.test.js:23:13)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 1.63 s, estimated 2 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?
How can I return the response?
I wasn't getting any response by passing the app url to the server variable. So I changed my syntax to ES6 import instead of the require and used the app module rather than the url for the application server as in the chai-http documentation here
So my code structure transitioned to
import chai from 'chai'
import chaiHttp from 'chai-http'
import server from '../app'
chai.use(chaiHttp)
describe('set up test', () => {
it('set up test', () => {
expect(1).toEqual(1)
})
})
describe('index route test', () => {
it('gives welcome message', (done) => {
chai
.request(server)
.get('/')
.then((res) => {
expect(res.statusCode).toBe(200)
done()
})
})
})
This way, I can capture the response to run checks on it. And my tests pass and required. The output is:
> jest --forceExit || true
PASS tests/index.test.js
set up test
✓ set up test (4 ms)
index route test
✓ gives welcome message (77 ms)
console.log
Application Server is up and running on port 8000
at Server.<anonymous> (app.js:43:11)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.189 s
Ran all test suites.
Force exiting Jest: Have you considered using `--detectOpenHandles` to detect async operations that kept running after all tests finished?
On OSX and Linux, the following works great (this is as simplified a reproduction as possible, hopefully without sacrificing meaning):
import { expect } from 'chai';
import { MongoClient, Db } from 'mongodb';
import { Application, Request } from 'express';
import { Server } from 'http';
import * as config from 'config';
describe('some test', () =>
{
let Session:{ new(app:Application):Request } = require('supertest-session'),
app:Application,
server:Server,
mongoClient:MongoClient,
db:Db;
beforeEach(async () =>
{
app = express();
server = app.listen(config.Http.port);
request = new Session(app);
// On Windows tests are executed before this resolves
mongoClient = await MongoClient.connect(config.Database.connectionOptions.url);
db = mongoClient.db(config.Database.connectionOptions.database);
});
afterEach(async () =>
{
await db.dropDatabase();
request.destroy();
server.close();
});
it('works like it oughtta', () =>
{
request.post('/api/account/login')
.send({ email: 'me#example.com', password: 'password' })
.expect(200)
.then((res) =>
{
expect(res.success).to.eq(true);
})
})
});
On a Windows machine, the above fails with the following output from npm:
13 verbose stack Exit status 4
13 verbose stack at EventEmitter.<anonymous> (C:\Path\To\AppData\Roaming\nvm\v9.4.0\node_modules\npm\node_modules\npm-lifecycle\lib\index.js:285:16)
... rest of stack
13 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:220:5)
If I take the database connection out of the beforeEach hook and do this instead, the test will run and pass, but I still notice hard-to-track-down failures in further tests with async hooks:
before(function(done)
{
MongoClient.connect(function(err, client)
{
mongoClient = client;
done();
});
});
after(function(done)
{
mongoClient.close(function() { done(); });
});
I've seen this behavior using Mocha, Jest and FuseBox test runners. Running node#9.4 on both machines. The solution to this cannot be "just make sure I test my tests on a Windows machine before I push".
Since we do not get any helpful support from help.heroku.com we make the last try here.
We are developping a classic web app consisting of:
----- ----- ----
|WEB| <----> |API| <----> |DB|
----- ----- ----
We currently are working with the following Heroku Dynos/Datastores
Heroku Postgres: Hobby Basic
Heroku API Dyno: Hobby
Heroku WEB Dyno: Hobby
The tech stack is:
runtime: nodejs (4.4.0)
db: postgres (9.6.1)
testframework: jasminejs (2.5.1)
query builder: knexjs (0.10.0)
We recently moved to from self hosted docker environment to Heroku and configured the Herokus CI pipeline which works fine for unit testing - but not integration testing.
The tests sporadically fails with timeouts (in average every 3rd test of the same commit). This is not stable enough to build up CI/CD.
Here the error messages we get:
**************************************************
* Failures *
**************************************************
1) integration test collections repository create() should return AUTHENTICATION_REQUIRED if user is anonymous
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
2) integration test collections repository create() should return AUTHORIZATION_REQUIRED if user is not space MEMBER
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
3) integration test collections repository create() should return collection if user is space MEMBER
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
For testing purposes we configured knex connection pooling to use one connection only:
export default {
client: 'pg',
connection: DB_URL,
pool: {
max: 1,
min: 1
}
};
The typical integration test setup is:
describe('integration test', () => {
describe('collections repository create()', () => {
//////////////////////////////////////////////////////////////////////
//Before the test block is execute all fixtures are loaded into the DB
//////////////////////////////////////////////////////////////////////
beforeAll((callback) => {
seed(path.join(__dirname, 'fixtures'))
.then(callback)
.catch((error) => {
fail(error);
callback();
});
});
////////////////////////////////////////////////////////////////////////
//Truncate all data from the DB before the next test block gets executed
////////////////////////////////////////////////////////////////////////
afterAll(resetDB);
it('should return AUTHENTICATION_REQUIRED if user is anonymous', (callback) => {
testUtils.testAsync({
callback,
catchTest: (error) => {
expect(error).toEqual(jasmine.any(repositoryErrors.AUTHENTICATION_REQUIRED));
},
runAsync: create({ props: { space: { id: 1 } } })
});
});
it('should return AUTHORIZATION_REQUIRED if user is not space MEMBER', (callback) => {
testUtils.testAsync({
callback,
catchTest: (error) => {
expect(error).toEqual(jasmine.any(repositoryErrors.AUTHORIZATION_REQUIRED));
},
runAsync: create({ props: { space: { id: 1 } }, userId: 1 })
});
});
it('should return collection if user is space MEMBER', (callback) => {
testUtils.testAsync({
callback,
runAsync: create({ props: { space: { id: 1 } }, userId: 2 }),
thenTest: (outcome) => {
expect(outcome).toEqual({ id: '1' });
}
});
});
...
seed:
const tableOrder = [
'users',
'guidelines',
'collections',
'spaces',
'details',
'files',
'guidelinesAuthors',
'collectionsUsers',
'spacesUsers',
'guidelinesCollections',
'spacesCollections',
'guidelinesDetails',
'guidelinesFiles',
'comments',
'commentsReplies',
'notifications'
];
export default function seed(path) {
return db.raw('BEGIN;')
.then(() => {
return tableOrder.reduce((promise, table) => {
return promise
.then(() => slurp({ path, table }))
.then(() => {
updateIdSequence(table)
.catch((error) => {
// eslint-disable-next-line no-console
console.log(
`Updating id sequence for table '${table}' failed! Error: `,
error.message
);
});
});
}, Promise.resolve()).then(() => db.raw('COMMIT;'));
})
.catch((error) => {
// eslint-disable-next-line no-console
console.log('SEED DATA FAILED', error);
return db.raw('ROLLBACK;');
});
}
...
resetDB:
export default function resetDB(callback) {
const sql = 'BEGIN; '
+ 'SELECT truncateAllData(); '
+ 'SELECT restartSequences(); '
+ 'COMMIT;';
return db.raw(sql)
.then(callback)
.catch((error) => {
// eslint-disable-next-line no-console
console.log('TRUNCATE TABLES FAILED', error);
return db.raw('ROLLBACK;');
});
}
Until now, these test have been running on local machines (Linux/Mac) and Codeship without any problem.
After almost two weeks of trying to get this work we made zero progress on this issue. I can't see anything wrong with this configuration and I start to belive Heroku has a serious issue with the datastores...
Has anybody experienced similar issues on Heroku?
Any idea what we can try else to get this work?