Mock an external endpoint in NodeJs by Moxios - node.js

I am trying to mock an external endpoint of Twilio in the unit test by Moxios library. I am also using SuperTest library to provide the exceptions of the test.
My internal endpoint which is called by Front-end is:
router.get('/twilio', async (req, res, next) => {
const result = await validatePhoneNumber(68848239, 'SG');
res.status(200).json(result);
});
and validatePhoneNumber is a function which calls the external endpoint by Axios which I am trying to mock and not to call the actual endpoint during the test:
const validatePhoneNumber = async (phone, code) => {
const endpoint = `https://lookups.twilio.com/v1/PhoneNumbers/${phone}?CountryCode=${code}`;
try {
const { status } = await axios.get(endpoint, {
auth: {
'username': accountSid,
'password': authToken
}
});
console.log('twilio', phone, status);
return {
isValid: status === 200,
input: phone
};
} catch (error) {
const { response: { status } } = error;
if (status === 404) {
// The phone number does not exist or is invalid.
return {
input: phone,
isValid: false
};
} else {
// The service did not respond corrctly.
return {
input: phone,
isValid: true,
concerns: 'Not validated by twilio'
};
}
}
};
And my the unit test code:
const assert = require('assert');
const request = require('supertest');
const app = require('../app');
const axios = require('axios');
const moxios = require('moxios');
describe('some-thing', () => {
beforeEach(function () {
moxios.install()
})
afterEach(function () {
moxios.uninstall()
})
it('stub response for any matching request URL', async (done) => {
// Match against an exact URL value
moxios.stubRequest(/https:\/\/lookup.twilio.*/, {
status: 200,
responseText: { "isValid": true, "input": 68848239 }
});
request(app)
.get('/twilio')
.expect(200, { "isValid": true, "input": 68848239 }, done);
});
});
If in my case Moxios is the right way to mock any external endpoints, I am getting the error below:
Error: Timeout of 3000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (c:\Source\Samples\Twilio\myproject\test\twilio.test.js)
I increased the time out to 10000 but still I get a same error.
Appreciate any hint or help.

I tried different ways but I prefer to go on with axios-mock-adapter library to mock any request through Axios.
Example:
const app = require('../app');
const axios = require('axios');
const request = require('supertest');
const MockAdapter = require('axios-mock-adapter');
describe('Valid phone number', () => {
it('Should return data from response', (done) => {
let mockAdapter = new MockAdapter(axios);
mockAdapter.onGet(twilioEndpoint)
.reply(200);
request(app)
.post('/api/validation')
.set('Content-Type', 'application/json')
.send(JSON.stringify(configuration))
.expect(200, { "isValid": true, "input": "68848239" }, done);
});
});
More information here

Related

Jest run async endpoints beforeall, afterall tests

[Junior dev!!]
->Node v18.08
->jest 29.0.1
->MySQL
Hi i m running several jest test in diferent folders of each endpoint on my API. I have several files where i m creating an account => test several endpoints => clean DB.. I have at least 14 files like these. So.. When an endpoint in those 14 file is not working the test doesn't go until the end and clean the DB so i need to go back and change the user name.. A clearly waste of time..
I'm learning about the Hook beforeAll and afterAll but they are not working.
i will show here my code without the hooks (test working)
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//Creating User
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
//Endpoints to test
test("POST/test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
//Deleting all info from DB
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
And when i want to add the hooks doesn't work
const axios = require("axios");
const API_URL = process.env.API_TEST_URL;
let TOKEN;
//creating before all an user
beforeAll(() => {
test("POST test register user", async () => {
const data = {
pseudo: "TEST",
password: "123",
date: "2022-08-29 16:31:25",
};
const url = `${API_URL}/register`;
const response = await axios.post(url, data);
expect(response.status).toBe(200);
expect(response.data).toHaveProperty("token");
TOKEN = response.data.token;
});
});
//testing endpoints
test("POST/action test", async () => {
const url = `${API_URL}/data/actions`;
const data = {
action: "running",
date: "2021-09-30 18:14:24",
};
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.post(url, data, { headers });
expect(response.status).toBe(200);
});
// if the endpoint doesn't work afterAll clean all DB
afterAll(() => {
test("DELETE test account", async () => {
const url = `${API_URL}/user/delete/all-data`;
const headers = { Authorization: `Bearer ${TOKEN}` };
const response = await axios.delete(url, { headers });
expect(response.status).toBe(200);
});
});
what do you think. Could you help me ? Because the info on jest is only showing how to do it with a function.. Can we test something inside beforeAll & afterAll ??
I have something similar in a project that I'm working on.
I had to make sure that the beforeAll was an async function and put the await.
beforeAll(async () => {
connection = await createConnection();
await connection.runMigrations();
const id = uuidv4();
const password = await hash('admin', 8);
await connection.query(
`insert into users (id, name, email, password, is_admin, created_at, driver_license)
values ('${id}', 'Admin', 'admin#test.com.br', '${password}', true, 'now()', 'XXXXXXX')`
);
});
afterAll(async () => {
await connection.dropDatabase();
await connection.close();
});
Another thing is that I have done a test using a test function inside the beforeAll and it has returned an error, so maybe just executing the post without the test may work.
Edit: Reading the docs about the test inside the beforeAll and beforeEach, it says that you can not use a test inside it.

Keep getting the 'cannot set headers' with Axios on Firebase Functions which still fully executes regardless of the error

I have a Firebase function that executes on a Stripe webhooks via express. The function executes fine but the sending of the email (using Axios) keeps resulting in an error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
It does work fine on localhost but the error appears when pushed to Firebase staging server. Weirdly the whole function executes fully including the Axios call where I'm getting the header issue (sending the email). It does take about 2-3 minutes to fully execute due to the error.
I've tried a number of different methods using return, and then() promises but it's still flagging this error. My code is as follows:
index.js
// Controllers
const stripeWebhookSubscription = require("./src/controllers/stripe/webhooks/subscription");
// Firebase
const admin = require("firebase-admin");
const functions = require("firebase-functions");
// Express
const express = require("express");
const cors = require("cors");
// Stripe
const stripe = require("stripe")(functions.config().stripe.key_secret);
const serviceAccount = require(functions.config().project.service_account);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: functions.config().project.database_url,
storageBucket: functions.config().project.storage_bucket,
});
const database = admin.firestore();
// -------------------------
// Stripe
// -------------------------
const stripeFunction = express();
stripeFunction.use(cors({origin: true}));
stripeFunction.post("/webhooks", express.raw({type: "application/json"}), (req, res) => {
const sig = req.headers["stripe-signature"];
let event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody,
sig,
functions.config().stripe.webhook_secret
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case "customer.subscription.created":
stripeWebhookSubscription.createSubscription(database, event.data.object, res);
break;
(...)
default:
console.log(`Unhandled event type ${event.type}`);
break;
}
res.json({received: true});
});
exports.stripe = functions.https.onRequest(stripeFunction);
subscription.js
const functions = require("firebase-functions");
const stripe = require("stripe")(functions.config().stripe.key_secret);
const axios = require("axios");
class stripeWebhookSubscription {
static createSubscription(database, subscription, res) {
let barcode;
let plan;
let merchant;
let customer;
database.collection("subscriptions").add({
id: subscription.id,
customer: subscription.customer,
status: subscription.status,
price: {
amount: (subscription.items.data[0].price.unit_amount / 100).toFixed(2),
interval: subscription.items.data[0].price.recurring.interval,
interval_count: subscription.items.data[0].price.recurring.interval_count,
},
product: subscription.items.data[0].price.product,
created: subscription.created,
current_period_start: subscription.current_period_start,
current_period_end: subscription.current_period_end,
cancel_at: subscription.cancel_at,
cancel_at_period_end: subscription.cancel_at_period_end,
payment_gateway: "stripe",
current_usage: 1,
})
.then((doc) => {
barcode = doc.id;
return database.collection("plans").where("stripe.product", "==", subscription.items.data[0].price.product).limit(1).get();
})
.then((docs) => {
docs.forEach((doc) => {
return plan = doc.data();
});
})
.then(() => {
return database.collection("subscriptions").doc(barcode).set({
merchant: plan.merchant,
}, {merge: true});
})
.then(() => {
return database.collection("merchants").doc(plan.merchant).get();
})
.then((doc) => {
return merchant = doc.data();
})
.then(() => {
async function stripeCustomer() {
const stripeData = await stripe.customers.retrieve(subscription.customer);
customer = stripeData;
}
return stripeCustomer().then(() => {
return customer;
});
})
.then(() => {
return database.collection("customers").doc(subscription.customer).set({
name: customer.name,
email: customer.email,
phone: customer.phone,
delinquent: customer.delinquent,
created: customer.created,
livemode: customer.livemode,
merchant: plan.merchant,
subscriptions: [barcode],
}, {merge: true});
})
.then((doc) => {
return axios.request({
url: "https://api.sendinblue.com/v3/smtp/email",
method: "post",
headers: {
"api-key": functions.config().sendinblue.key,
"Content-Type": "application/json",
},
data: {
"to": [
{
"email": customer.email,
"name": customer.name,
},
],
"replyTo": {
"email": "support#scanable.com.au",
"name": "Scanable",
},
"templateId": 2,
"params": {
"plan_name": plan.name,
"interval_count": plan.interval_count,
"interval": plan.interval,
"subscription": barcode,
"merchant_name": merchant.name,
"merchant_email": merchant.email,
},
},
})
.then((response) => {
return console.log("Membership email sent to " + customer.email);
});
})
.then(() => {
res.status(200).send("✅ Subscription " + subscription.id + " created!");
})
.catch((err) => {
res.status(400).send("⚠️ Error creating subscription (" + subscription.id + "): " + err);
});
}
}
module.exports = stripeWebhookSubscription;
In index.js, you call this line:
stripeWebhookSubscription.createSubscription(database, event.data.object, res);
immediately followed by this line:
res.json({received: true});
By the time the createSubscription path has finished, the response has already been sent. When deployed to Cloud Functions, this will also terminate your function before it's done any of its workload (Note: this termination behaviour is not simulated by the local functions emulator).
Depending on what you are trying to achieve, you can probably just add the missing return to this line so that the res.json({received: true}) never gets called:
return stripeWebhookSubscription.createSubscription(database, event.data.object, res);
Additionally, on this line in subscription.js:
database.collection("subscriptions").add({
you need to add the missing return statement so the asynchronous tasks are properly chained:
return database.collection("subscriptions").add({

fake response(500) with sinon in post request NodeJs

can anyone help me out creating a fake response (500) for testing my API using "sinon" , am new to nodeJs , i have tried to test where the return status is 201 and it worked however am still not able to make the fake 500 response
here is my code , thank you in advance
//knex
app.post("/api/categories", function (req, rep) {
knex('categories').insert(req.body)
.then(() => rep.sendStatus(201).json({ message: "Category inserted" }))
.catch((err) => {
console.log(err)
rep.status(500);
})
});
// in my test js
var request=require('supertest');
var KnexApp=require('../KnexFolder/app');
var sinon = require("sinon");
describe("POST/users", ()=>{
describe('when everything is fine and no errors', () => {
it('should respond with status 201',async () => {
const res = await request(KnexApp)
.post('/api/categories')
.send({
name:"from test",
img_id: 5
})
expect(res.statusCode).toEqual(201)
})
})
describe('when There is internal server error', () => {
it('should respond with status 500',async () => {
sinon.stub('/api/categories', "post").throws(
new Error({
response: { status: 500},
})
);
expect(res.statusCode).toEqual(500)
})
})
})
There are two testing strategies:
Stub knex, query interface, and the resolved/rejected value. This way is easier than the second way, you don't need to set up a real testing database and populate testing data.
As mentioned above, you need to set up a real testing database(run your migration script to create database and tables, create the seed testing data, etc...)
I will use the first way to test your code. Since sinon doesn't support stub a function export defaults by a module. We need to use proxyquire package.
app.js:
const express = require('express');
const knex = require('knex')({
client: 'mysql',
connection: {
host: '127.0.0.1',
port: 3306,
user: 'your_database_user',
password: 'your_database_password',
database: 'myapp_test',
},
});
const app = express();
app.use(express.json());
app.post('/api/categories', function (req, rep) {
console.log(req.body);
knex('categories')
.insert(req.body)
.then(() => rep.sendStatus(201).json({ message: 'Category inserted' }))
.catch((err) => {
console.log(err);
rep.sendStatus(500);
});
});
module.exports = app;
app.test.js:
const request = require('supertest');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('POST/users', () => {
describe('when everything is fine and no errors', () => {
it('should respond with status 201', async () => {
const queryInterfaceStub = {
insert: sinon.stub().resolves(),
};
const knexStub = sinon.stub().returns(queryInterfaceStub);
const KnexStub = sinon.stub().returns(knexStub);
const KnexApp = proxyquire('./app', {
knex: KnexStub,
});
const res = await request(KnexApp).post('/api/categories').send({
name: 'from test',
img_id: 5,
});
sinon.assert.match(res.statusCode, 201);
});
});
describe('when There is internal server error', () => {
it('should respond with status 500', async () => {
const queryInterfaceStub = {
insert: sinon.stub().rejects(new Error('fake error')),
};
const knexStub = sinon.stub().returns(queryInterfaceStub);
const KnexStub = sinon.stub().returns(knexStub);
const KnexApp = proxyquire('./app', {
knex: KnexStub,
});
const res = await request(KnexApp).post('/api/categories').send({});
sinon.assert.match(res.statusCode, 500);
});
});
});
Test result:
POST/users
when everything is fine and no errors
{ name: 'from test', img_id: 5 }
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:558:11)
at ServerResponse.header (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:771:10)
at ServerResponse.send (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/express/lib/response.js:267:15)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/71565119/app.js:20:37
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
✓ should respond with status 201 (436ms)
when There is internal server error
{}
Error: fake error
at Context.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/src/stackoverflow/71565119/app.test.js:27:38)
at callFn (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:364:21)
at Test.Runnable.run (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runnable.js:352:5)
at Runner.runTest (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:677:10)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:801:12
at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:594:14)
at /Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:604:7
at next (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:486:14)
at Immediate.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/expressjs-research/node_modules/mocha/lib/runner.js:572:5)
at processImmediate (internal/timers.js:461:21)
✓ should respond with status 500
2 passing (448ms)

How to thrown error in catch block using sinon stub

I am using mocha and sinon for test the node services, In controller I have getToken npm module for getting the token with name and value as parameters and in spec file I trying to send empty name as parameter using withargs but the response getting success excepted result is token creating fail please help on this issue.
controller.ts
import {getToken} from './tokenUtil';
export async function userInfo(req:Request,res:Response){
try{
let token = await getToken(name,value);
}
catch(error){
res.send({status:'Failue',message:error});
return
}
res.send({status:'success',message:'token creation success'})
}
tokenUtil.ts
export async function getToken(name,value){
// token code here
}
token.spce.ts
import * as sinon from 'sinon';
import * as proxyquire from 'proxyquire';
describe('get token',()=>{
let req:any;
let res:any;
beforeEach(()=>{
res={
send:sinon.stub();
}
it('getting error when given empty name',async()=>{
let tokenStub = sinon.stub().withArgs('',tokenValue).returns(undefined);
let tokenctl=proxyquire('./controller',{
'./tokenUtil':tokenStub
})
await tokenctl.userInfo(req,res);
sinon.assert.calledWithExactly(res.send,{status:'Failue',message:'token creating fail'})
})
})
})
You are testing the controller.ts module, so the test file name should be controller.spec.ts or controller.test.ts.
Since the ./tokenUtil use named exports, so the tokenStub should be an object.
You should use sinon.stub().rejects() to create a promise stub with rejected value.
E.g.
controller.ts:
import { getToken } from './tokenUtil';
import { Request, Response } from 'express';
export async function userInfo(req: Request, res: Response) {
const { name, value } = req.body;
try {
let token = await getToken(name, value);
res.send({ status: 'success', message: 'token creation success' });
} catch (error) {
res.send({ status: 'Failue', message: error });
}
}
tokenUtil.ts:
export async function getToken(name, value) {
// token code here
}
controller.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
describe('get token', () => {
let req: any;
let res: any;
beforeEach(() => {
res = {
send: sinon.stub(),
};
});
it('should create token success', async () => {
req = { body: { value: '123', name: 'teresa teng' } };
let tokenStub = {
getToken: sinon.stub().withArgs(req.body.name, req.body.value).resolves(),
};
let tokenctl = proxyquire('./controller', {
'./tokenUtil': tokenStub,
});
await tokenctl.userInfo(req, res);
sinon.assert.calledWithExactly(res.send, { status: 'success', message: 'token creation success' });
});
it('should handle error when given empty name', async () => {
const tokenValue = '123';
req = { body: { value: tokenValue, name: '' } };
const error = new Error('token creating fail');
let tokenStub = {
getToken: sinon.stub().withArgs('', tokenValue).rejects(error),
};
let tokenctl = proxyquire('./controller', {
'./tokenUtil': tokenStub,
});
await tokenctl.userInfo(req, res);
sinon.assert.calledWithExactly(res.send, { status: 'Failue', message: error });
});
});
Test result:
get token
✓ should create token success (101ms)
✓ should handle error when given empty name
2 passing (112ms)

Setting Bearer Token in the header section while testing with Supertest

I'm trying to add the Bearer in the header section in POST request while testing with Supertest. I tried many methods. I'm a beginner in testing. Please suggest some better ways to achieve this.
here is my sample code. What is wrong with this?
it('POST/userAuth', async (done) => {
const res = await request
.post('/v1/user/haha')
.set('Authorization', `bearer ${Token}`)
.send({
title: 'Some random text',
options: [
{ start: hello, end: world },
{ start: good, end: bye },
],
});
You can set a request header like this:
const request = require('supertest');
const express = require('express');
const app = express();
const TOKEN = 'some_token';
describe('POST /some-url', function() {
it('does something', function(done) {
request(app)
.post('/some-url')
.send({ body: 'some-body' })
.set('Authorization', `Bearer ${TOKEN}`)
.expect(200, done);
});
});
in superagent docs you can find specialized .auth method
interface Request extends Promise<Response> {
auth(user: string, pass: string, options?: { type: "basic" | "auto" }): this;
auth(token: string, options: { type: "bearer" }): this;
...
}
(supertest is using superagent under the hood)
I prefer to set auth in before function for all tests in the set.
import * as Koa from 'koa';
import * as http from 'http';
import { agent as superagent } from 'supertest';
import { UserController } from './user-controller';
const testBearerToken = 'test-bearer-token';
describe('user controller', async () => {
context('simple user', async () => {
it('should save user', async () => {
const response = await test
.post('/v1/user/haha')
// you can here, but rather set it in the before function
//.auth(testBearerToken, { type: 'bearer' });
.send({
title: 'Some random text',
options: [
{ start: 'hello', end: 'world' },
{ start: 'good', end: 'bye' },
],
});
// .expect(...)
// expect(response)...
});
});
let test;
before(async () => {
const app = new Koa();
const userController = new UserController();
app.use(userController.middleware());
test = superagent(http.createServer(app.callback()))
.auth(testBearerToken, { type: 'bearer' });
});
});

Resources