- You must have two databases created (nodejs y nodejs_test)
- Config database permisions in .env-cmdrc (file)
npm install
npm run reset-database
npm run dev
npm run test
1. Component-based architecture
Each folder represents a component, in this case tasks and users.
It doesn't mean that it represents each table in the DB, this depends of the software requirements.
The component-based architecture was chosen, to have a more intuitive order to change a requirement. If I need to change something, I open every folder and files involved with that change, It takes more time.
Thanks to middlewares, we can implemented a cascade based filtration.
First: I can verify the authentication.
Second: Satinize or validate the input-data of the user.
Third: Do operations in the controller and DB.
This is the file to authenticate.
Each models represent each database table.
When an integration test is carried out, it is verified that the data was stored correctly in the database and that the services work well. If a table depends on another, for example tasks and user, the user must be registered first. Then it is necessary to make the reusable tests.
A good practice is has the test inside in each component, the test is divided in two parts:
-
Request: They are the requests to the API
-
Test: Management the requests and check their states.
Example of Request "UserRequest.js"
const userData = {
username: "carlos",
password: "carlos123",
}
class UserRequests {
constructor(appReq) {
this.appReq = appReq
}
async create() {
const apiRes = await this.appReq.post('/api/user/').send({
...userData
})
return apiRes
}
...
}
module.exports = (arg1) => { return new UserRequests(arg1) }
Example of Test "UserTest.js"
const request = require('supertest')
const app = require('../../../app')
const appReq = request(app)
const { User } = require('../../../models')
const UserRequests = require('./UserRequests')(appReq) // Uso de los request
beforeEach(async()=>{
await User.destroy({where:{},truncate:true})
})
test('Should register a user', async() => {
const res = await UserRequests.create() // Esperamos la respuesta
expect(res.status).toBe(201)
expect(typeof res.body.token).toBe('string')
})
Now I
User UserRequests is now reused before register a task "TaskTest.js"
require('../../../utils/constants')
const {Op} = require('sequelize')
const request = require('supertest')
const app = require('../../../app')
const appReq = request(app)
const { User, Task } = require('../../../models')
const TaskRequests = require('./TaskRequests')(appReq)
const UserRequests = require('../../user/__test__/UserRequests')(appReq) // Reutilización
let token = null
beforeEach(async()=>{
await User.destroy({where:{},truncate:true})
await Task.destroy({where:{},truncate:true})
const res = await UserRequests.create() // Se crea un usuario antes de cada test
token = res.body.token
})
test('Should register a task', async() => {
const res = await TaskRequests.create(token)
expect(res.status).toBe(201)
})
If a fatal error occurs it is necessary to send a message to the administrator or save it in a log.
Therefore express-async-handler was used to wrap any function and listen for errors.
It was used in the controllers.
const asyncHandler = require('express-async-handler')
exports.create = asyncHandler(async (req, res) => {
...
})
Any error within the function asyncHandler will be reported.
For this reason, the app.js file can handle all the errors of the application.
...
app.use(async(err, req, res, next) => {
const {isOperationalError, httpCode, description} = await handleError(err);
console.log({isOperationalError, httpCode, description})
res.status(httpCode).send(description)
})
handlerError handles all encapsulated errors.
You must execute
npm run reset-database
The software will empty => creating tables and populating them with static data.
Compare two codes to disable a user with an ORM.
Example 1:
await Task.update({
FKStatus: 2,
}, { where: { Id: id } })
Example 2:
await Task.update({
FKStatus: global.STATUS.INACTIVE,
}, { where: { Id: id } })
In the database 1 es ACTIVE and 2 is INACTIVE
Example 1 is not intuitive, if after a week you look at it maybe you don't remember what the hell means 2.
Example 2 is more intuitive, Therefore It's easier to read and understand.