About: Founder, Software Architect, Fullstack Developer, Mobile App Developer, Data Engineer, Data Scientist.
I don't have Instagram.
Joined:
Oct 1, 2019
A recommended way to do functional testing with K.O architecture
Publish Date: Nov 8 '19
9 1
In my last article, I introduced a new architecture pattern to improve the productivity of express.js API development. Functional testing with the new architecture requires another article to discuss, so here it is.
How to read this article
It is recommended to read my previous article before continuing.
The model arguments in the functions above are not that simple to do mocking. Feel free to try it out and leave a code snippet in the comment if you made it.
A recommended way
A recommended way to test K.O architecture is to use an HTTP assertion library to do bottom-up integration testing. By doing so we can avoid creating complex mock functions.
Using the post HTTP method is more convenient as we utilize req.body as the function argument carrier.
Let's test the express app.
test/response.test.js
functiontest(funcStr,args){returnrequest(app).post('/'+funcStr).send(args)}describe ('response',function (){describe('errorRes',function (){it('should return default status code & message',function (done){test('errorRes',['error']).expect(500,{success:false,error:'failed operation'},done)})it('should return custom errMsg',function (done){test('errorRes',['error','test']).expect(500,{success:false,error:'test'},done)})it('should return custom errMsg & status code',function (done){test('errorRes',['error','test',401]).expect(401,{success:false,error:'test'},done)})})describe('successRes',function (){it('should return default status code & data',function (done){test('successRes',[]).expect(200,{success:true,data:{}},done)})it('should return custom data',function (done){test('successRes',[{custom:true}]).expect(200,{success:true,data:{custom:true}},done)})it('should return custom data & status code',function (done){test('successRes',[{custom:true},201]).expect(201,{success:true,data:{custom:true}},done)})})describe('errData',function (){it('should return default status code & error',function (done){test('errData',{err:true}).expect(500,{success:false,error:'failed operation'},done)})it('should return default status code & data',function (done){test('errData',{err:false}).expect(200,{success:true,data:{custom:true}},done)})it('should return custom error message',function (done){test('errData',{err:true,errMsg:'custom'}).expect(500,{success:false,error:'custom'},done)})})})
Let's create another minimal express app to test functions in common/crud.js.
test/curd.test.js
const{create,read,update,remove}=require('../common/crud')constRestaurant=require('../models/Restaurant')constUser=require('../models/User')constrequest=require('supertest')constexpect=require('chai').expectconstbodyParser=require('body-parser')constexpress=require('express')constmongoose=require('mongoose')// check the updated github readme for exampleconst{testMongoUrl}=require('../config')constapp=express()mongoose.connect(testMongoUrl,{useNewUrlParser:true,autoIndex:false,useFindAndModify:false,useUnifiedTopology:true,})app.use(bodyParser.json()).post('/create',create(Restaurant,['owner'])).post('/read',read(Restaurant,['owner'])).post('/update/:_id',update(Restaurant,['owner'])).post('/remove/:_id',remove(User))
Using a new mongo database (testMongoUrl) to do testing is more convenient to remove testing data after each test run and not interfere with existing data.
functiontest(funcStr,args){returnrequest(app).post('/'+funcStr).send(args)}describe('crud',function (){constuser={"name":"Test","email":"test@test.com","type":"admin","password":"12345"}constrestaurant={"name":"Brand New Restaurant","location":{"type":"Point","coordinates":[-73.9983,40.715051]},"available":true}consttestUser=newUser({_id:newmongoose.Types.ObjectId(),...user})before(function (done){testUser.save(done)})after(function (done){mongoose.connection.db.dropDatabase(function(){mongoose.connection.close(done)})})describe('create',function (){it('should not create new restaurant without owner',function (done){constdata=restauranttest('create',data).expect(500,done)})it('should return new restaurant and populate',function (done){constdata={owner:testUser._id,...restaurant}test('create',data).expect(200).expect(function (res){restaurant._id=res.body.data._idexpect(res.body.data).to.be.a('object')expect(res.body.data).to.deep.include(restaurant)expect(res.body.data.owner.name).to.equal('Test')expect(res.body.data.owner.password).to.equal(undefined)}).end(done)})})describe('read',function (){it('should return restaurant and populate',function (done){constdata=[{"available":true,_id:restaurant._id}]test('read',data).expect(200).expect(function (res){expect(res.body.data).to.be.a('array')expect(res.body.data[0]).to.deep.include(restaurant)expect(res.body.data[0].owner.name).to.equal('Test')expect(res.body.data[0].owner.password).to.equal(undefined)}).end(done)})it('should return no restaurant',function (done){constdata=[{"available":false}]test('read',data).expect(200).expect(function (res){expect(res.body.data.length).to.equal(0)}).end(done)})})describe('update',function (){it('should return updated restaurant and populate',function (done){const{_id}=restaurantconstdata={name:'New name'}test('update/'+_id,data).expect(200).expect(function (res){expect(res.body.data).to.be.a('object')expect(res.body.data).to.deep.include({...restaurant,...data})expect(res.body.data.name).to.equal('New name')expect(res.body.data.owner.name).to.equal('Test')expect(res.body.data.owner.password).to.equal(undefined)}).end(done)})})describe('remove',function (){it('should remove user',function (done){const{_id}=testUsertest('remove/'+_id).expect(200).expect(function (res){expect(res.body.data).to.be.a('object')expect(res.body.data).to.have.property('ok')expect(res.body.data.ok).to.equal(1)}).end(done)})})})
Let's create another minimal express app to test functions in common/middleware.js.
test/middleware.test.js
const{notFound,onlyAdmin,notOnlyMember}=require('../common/middleware')constrequest=require('supertest');constbodyParser=require('body-parser');constexpress=require('express');constapp=express();app.use(bodyParser.json()).post('/notFound',notFound).post('/onlyAdmin',addUserType,onlyAdmin,notFound).post('/notOnlyMember',addUserType,notOnlyMember,notFound)functionaddUserType(req,res,next){req.user={type:req.body.type}next()}functiontest(funcStr,args){returnrequest(app).post('/'+funcStr).send({type:args})}describe('middleware',function (){describe('notFound',function (){it('should return not found',function (done){test('notFound').expect(404,done)})})describe('onlyAdmin',function (){it('should access',function (done){test('onlyAdmin','admin').expect(404,done)})it('should not access',function (done){test('onlyAdmin','member').expect(401,done)})})describe('notOnlyMember',function (){it('should access',function (done){test('notOnlyMember','admin').expect(404,done)})it('should access',function (done){test('notOnlyMember','owner').expect(404,done)})it('should not access',function (done){test('notOnlyMember','member').expect(401,done)})})})
End-to-end testing
After testing all functions in the common folder, it's up to you to continue end-to-end testing in the mocha framework or use other tools.
Feel free to leave a comment if you have any questions.