"It" statement keep using regular database instead of the test database - node.js

I am doing some testing and I just want to test my endpoint. What goes in and what does out that's it.
I have a test database that I want to use when the test run.
in "BeforeAll" I connect to the test database and in my beforeach I make a post of a user.
It work it's inserted in the test database
The problem is as soon as I try to make a request in a "It" statement , the database used is app one not the test database :/
beforeAll(async () => {
await mongoose.connect(testDatabase);
});
afterAll(async function () {
await mongoose.disconnect()
});
describe('/user', () => {
let app;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule, UserModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
// this send correctly data to the TEST database
enter code here
return request(app.getHttpServer())
.post('/user').send(TEST_USER)
.set('Accept', 'application/json')
.expect(201)
.then(r => console.log("Result of post", r.body))
});
it('GET', () => {
// my probleme here : this retrieve the regular database content (setup in main file) NOT THE TEST database setup in beforeAll
return request(app)
.get('/user')
.expect(200)
.then(r => console.log("Result of get", r.body))
});
});
I am doing it wrong ?
Thank you guys !

My bet is that your app is not using the connection you establish there - you app, during the request, most likely creates its own connection.
Could you show minimum reproduction repository with the mvp implementation of your route under test, including the module and dependencies?

Finally my mistake was in this line :
imports: [AppModule, UserModule],
App module override my test's mongoose. connect with another connection established inside.

Related

Testing Angular Services with MSW causes timeout

EDIT: I should mention, that I only have problems during testing. When I run ng serve and use msw to serve the data everything works correctly.
I stumbled upon mswjs recently and wanted to use the mock service workers to test my frontend services without waiting on the backend team and avoid having to write mock-service classes. I setup everything according to the examples provided in the documentation.
At first I got the message that stating spec 'UserService should get list of users' has no expectations.
I researched this and added a done() function call at the end of my subscribe callback. After doing that, I get the following error:
Error: Timeout - Async function did not complete within 3000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL) in node_modules/jasmine-core/lib/jasmine-core/jasmine.js (line 7609)
I already tried increasing the default_timout in Karma but even setting it to 30.000 did not change the result.
I also tried working around by using waitForAsync without any success. This way I get no error and the test succeeds but only because it still finds no expectations within the spec.
Most example I found online do not deal with mock service workers and instead resort to using mock-services and fakeasync which does not help in my case.
This is how my code looks like:
My Angular Service:
#Injectable({
providedIn: 'root'
})
export class UserService {
private url = 'http://localhost:3000/api/users';
constructor(private http: HttpClient) { }
getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.url}/${id}`);
}
listUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
}
My Test Code:
describe('UserService', () => {
let service: UserService;
beforeAll(() => {
worker.start();
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
});
service = TestBed.inject(UserService);
});
afterAll(() => {
worker.stop();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should get list of users', (done) => {
service.listUsers().subscribe((data) => {
expect(data.length).toBe(5);
done();
});
})
});
The Worker setup:
const handlers = [
rest.get('http://localhost:3000/api/users', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json(users))
})
]
export const worker = setupWorker(...handlers)
I managed to solve my own problem by using firstValueFrom and waitForAsync.
I changed the code in my tests to the following:
it('should get list of users', waitForAsync(async () => {
const source$ = service.listUsers();
const result = await firstValueFrom(source$);
expect(result.length).toEqual(5);
}));

Mocha/ Supertest is not exiting on completion of tests

I am using the mocha testing framework, and after running the following test it does not exit.
I have tried Promises and async await with no luck. --exit at the end of the mocha command works, but I want to find the source of the issue.
I am wondering if it is the knex database connection when running beforeEach and afterEach functions. However, I do not know how to disconnect the db connection other than destroy(), and if this is used the following tests do not run.
Can anybody see anything within the code that could be causing this issue? Or recommend another way to remedy this?
const app = require('../../app');
const request = require('supertest');
describe('Route: /' + route, () => {
let token = '';
let route = 'user';
before(function (done) {
const user = {email: 'admin#email.com', password: 'password'};
request(app)
.post('/login')
.send(user)
.end((err, res) => {
token = res.body.token;
done();
});
});
beforeEach(async () => {
await knex.migrate.rollback();
await knex.migrate.latest();
await knex.seed.run();
});
afterEach(() => knex.migrate.rollback());
it(`should not be able to consume /${route} since no token was sent`, (done) => {
request(app)
.get(`/${route}`)
.expect(401, done)
});
it(`should be able to consume /${route} since a valid token was sent`, (done) => {
request(app)
.get(`/${route}`)
.set('Authorization', 'Bearer ' + token)
.expect(200, done);
});
});
For anyone who comes across this and has a similar problem.
Using the following links;
- GitHub mocha debug example
- Mocha docs -exit
- wtfnode
I was able to debug the problem.
wtfnode used within my test showed me that my database was still connected with the console reading.
const wtf = require('wtfnode');
after(wtf.dump()); // place within test describe body
Returned;
- Sockets:
- 127.0.0.1:58898 -> 127.0.0.1:5432
- Listeners:
- connect: Connection.connect # <user_path>/node_modules/pg/lib/connection.js:59
I am using knex to connect to the database, so I've added code below to the file helper.js in my test directory.
/test/helper.js
const knex = require('../database/db');
before(function () {
if (!knex.client.pool) return knex.initialize();
});
beforeEach(async function () {
await knex.migrate.rollback();
await knex.migrate.latest();
await knex.seed.run();
});
afterEach(function () {
return knex.migrate.rollback()
});
after(function () {
return knex.destroy();
});

reset a database before each test

I'm using node and supertest for a simple app. I got SQlite3 for the local test database. I did a simple test to get a super inserted into the database. I wanted to reset the database each time a test is run. I'm looking in the docs right now and can't seem to locate it. I figured I would ask here because it seems someone would most likely know the info.
const request = require('supertest');
const server = require('../server');
describe('Authentication', function() {
//database reset here
it('should create a new user /users/registration', function(done) {
request(server)
.post('/users/register')
.send({
username: 'user-name',
email: 'luser-name#gmail.com',
password: '12345'
})
.set('Accept', 'application/json')
.expect(201, done);
});
});
If you want to run any piece of code before each test, you can use beforeEach function in jest
describe('my test', () => {
beforeEach(() => {
// code to run before each test
});
test('test 1', () => {
// code
});
test('test 2', () => {
// code
});
});
So best way to do this is have some logic in your routing functions of your Api
Receive an API request
Check if ['X-MOCK-HEADER'] exists
If it does then route to the mock version of the endpoint
So your mock for create user would always return 201 OK - your mock endpoint would do something like this:
const routes = {
CREATE_USER_OK:() => { return {....} } // make sure these return proper http responses
CREATE_USER_BAD_REQUEST: () { return {...} }
}
return routes[HEADER_VALUE]()
The reason being you're testing the route not the database class in this instance, so you just want to return static data, if you wanna test something else then just change the X-MOCK-HEADER value to whatever you want and add the mock route to return the right http response/code - I'd need to know what the API code looked like to help you on the backend implementation.
If possible stay away from messing with staging databases for testing because down the road you will suffer a LOT of pain as it gradually gets filled with garbage.
Also if you're working with a front end app you can quickly prototype with static data - this is especially useful if you've got a front end team waiting for an API endpoint to say create a login screen.
There's no defined way to reset a sqlite db, just delete the db and recreate.
Sqlite: How do I reset all database tables?
I did this in the file and it works fine
const request = require('supertest');
const server = require('../server');
const knex = require('knex');
const dbConfig = require('../knexfile.js')['test'];
const db = knex(dbConfig);
describe('Authentication', () => {
beforeEach(async () => {
await db('users').truncate();
});
it('should create a new user /users/registration', function(done) {
request(server)
.post('/users/register')
.send({
username: 'user-name',
email: 'luser-name#gmail.com',
password: '12345'
})
.set('Accept', 'application/json')
.expect(201, done);
});
});

How to mock/intercept calls to mongoDB atlas during tests? (cloud DB)

I have an express app (REST API) which connects to a mongoDB cluster on MongoDB Atlas (the cloud database) during tests. I'm using Mocha to test.
I have an end-to-end test (which uses the database) but for the majority of the tests I want to mock/stub the calls to the database so that it's isolated.
I've tried using nock to intercept the network connections and mock the response, but from what I can see nock is only for http calls and mongoDB Atlas uses DNS (starts with mongodb+srv:, see here for more info) and I think this is why I can't get this to work.
I've also trying to stub the Model. I'm struggling to get this working but it seems like it might be an option?
// The route
router.post('/test', async (req, res) => {
const { name } = req.body;
const example = new ExampleModel({ name: name})
// this should be mocked
await example.save();
res.status(200);
});
// The test
describe('POST /example', () => {
it('Creates an example', async () => {
// using supertest to make http call to my API app
const response = await request(app)
.post('/test')
.type("json")
.send({ 'name': 'test-name' })
// expect the model to have been created and then saved to the database
});
});
I'm expecting that when I run the test, it will make a POST to the API, which won't make a call to the database but will return fake data (as though it had).
I've found some really useful resources and sharing them:
Isolating mongoose unit tests (including model methods like findOne guide
Stubbing the save method on a model: Stubbing the mongoose save method on a model (I just used `sinon.stub(ExampleModel.prototype, 'save').
// example code
it('Returns 400 status code', async () => {
sinon.stub(ExampleModel, 'findOne').returns({ name: 'testName' });
const saveStub = sinon.stub(ExampleModel.prototype, 'save');
const example = new ExampleModel({ name: 'testName' })
const response = await request(app)
.post('/api/test')
.type("json")
.send({ name: 'testName' })
sinon.assert.calledWith(Hairdresser.findOne, {
name: 'testName'
});
sinon.assert.notCalled(saveStub)
assert.equal(response.res.statusCode, 400);
});

Mocha testing PostgreSQL with Knex is giving me a MigrationLocked error

I have got a local development environment working for Node/PostgreSQL/Knex in the sense that I can post to a development database on my machine using an API. I am now trying to create tests for this functionality but am getting an error.
Here's my config:
//knexfile.js
module.exports = {
development: {
client: 'pg',
connection: {
host: '127.0.0.1',
user: 'dbUser',
password: 'dbpword',
port: 5432,
database: 'example-name'
},
migrations: {
directory: __dirname + '/db/migrations'
},
seeds: {
directory: __dirname + '/db/seeds/development'
}
},
}
//db.js
const config = require('../knexfile.js');
const env = process.env.NODE_ENV || 'development';
const knex = require("knex")(config[env]);
module.exports = knex;
knex.migrate.latest([config]);
And then my tests:
import chai from 'chai';
import { expect } from 'chai';
import chaiHttp from 'chai-http';
import knex from '../../db/db';
import app from '../../server';
chai.use(chaiHttp);
describe('Tokens API', () => {
beforeEach((done) => {
knex.migrate.rollback()
.then(() => {
knex.migrate.latest()
.then(() => {
return knex.seed.run()
.then(() => {
done();
});
});
});
});
afterEach((done) => {
knex.migrate.rollback()
.then(() => {
done();
});
});
describe('POST /users', () => {
it('posts a list of users to the database with all mandatory fields', (done) => {
chai.request(app)
.post('/users')
.send({
"users": [
"steve",
"whoever",
"matt",
"another"]})
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res).to.be.json;
done();
});
});
});
});
When I run this I get the following error twice - I think for the knex calls in the beforeEach block:
Knex:warning - Can't take lock to run migrations: Migration table is already locked
Knex:warning - If you are sure migrations are not running you can release the lock manually by deleting all the rows from migrations lock table: knex_migrations_lock
Unhandled rejection MigrationLocked: Migration table is already locked
I have tried numerous things - including clearing out the knex_migrations_lock table. The only support I can find online is this thread, which suggests clearing out the lock table using DELETE FROM Migrations_lock where id <> 0;, however my lock table only has a is_locked column with zero values.
Any idea what's going on?
EDIT: I've just realised if you edit out all the knex calls, the test actually passes. Could this be because I am effectively calling knex twice - once from db.js and once indirectly through server.js? If that's the case, how do I avoid doing this - because surely I need to call the knex setup for Node to run it?
For anyone stumbling across this, the problem was actually coming from db.js, specifically the last line:
const config = require('../knexfile.js');
const env = process.env.NODE_ENV || 'development';
const knex = require("knex")(config[env]);
module.exports = knex;
knex.migrate.latest([config]);
Of course this is asynchronous, and the tests were importing this file before trying to run their own knex functions, causing the lock. I got round this by adding a clause to block this running while testing:
if(process.env.NODE_ENV != 'test') {
knex.migrate.latest([config])
}
You can then create a test environment by adding process.env.NODE_ENV='test' to each spec file, or by installing the npm env test module.
Had the exact same issue, ended up being due to my API calling the database when being initialized by the supertest library.
For example, my test file:
var db = require('../db');
var api = require('../api');
var supertest = require('supertest')(api);
describe('Session routes', () => {
beforeEach((done) => {
db.migrate.rollback()
.then(() => {
db.migrate.latest()
.then(() => {
return db.seed.run()
.then(() => {
done();
});
});
});
});
afterEach((done) => {
db.migrate.rollback()
.then(() => {
done();
});
});
it('GET /session should error with no token', (done) => {
supertest
.get('/session')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(401, {
error: 'Unauthorized'
}, done);
});
});
On line 2, it requires my api - when my api is required the following code gets run straight away to initialize my api's external services API:
var db = require('./other-postgres-library');
var servicesApi = require('./services/api')(db);
This would connect to a bunch of external services and write the results to the database.
So when tests were running my APP was throwing errors because it was trying to write to a database which was being rolled back/migrated/seeded etc.
I changed my inner services API to initialize lazily and all my problems have disappeared.
In your case, I would hazard to guess when your tests runs this line
import app from '../../server'; your app/server code is trying to running some queries against the database.

Resources