Writing unit tests for method that uses jwt token in javascript - javascript

I have been trying to write unit test in javascript for the method which uses jwt token validation. So the results are fetched only if the token is valid.
I want to mock the jwt token and return results. Is there any way to do it ? I tried using ava test framework, mock require, sinon but I am unable to do it.
Any thoughts ?
Code:
I am trying to mock jwt.verify
**unit test:**
const promiseFn = Promise.resolve({ success: 'Token is valid' });
mock('jsonwebtoken', {
verify: function () {
return promiseFn;
}
});
const jwt = require('jsonwebtoken');
const data = jwt.verify(testToken,'testSecret');
console.log(data)
**Error :**
ERROR
{"name":"JsonWebTokenError","message":"invalid token"}
So the issue here is that, its actually verifying the token but not invoking the mock.

Modules are singletons in Node.js. So if you required 'jwt' in your test and then it's required down in your business logic it's going to be the same object.
So pretty much you can require 'jwt' module in your test and then mock the verify method.
Also, it's important not to forget to restore the mock after the test is done.
Here is a minimal working example of what you want to accomplish (using ava and sinon):
const test = require('ava');
const sinon = require('sinon');
const jwt = require('jsonwebtoken');
let stub;
test.before(t => {
stub = sinon.stub(jwt, 'verify').callsFake(() => {
return Promise.resolve({success: 'Token is valid'});
});
})
test('should return success', async t => {
const testToken = 'test';
const testSecret = 'test secret';
const result = await jwt.verify(testToken, testSecret);
console.log(result);
t.is(result.success, 'Token is valid');
});
test.after('cleanup', t => {
stub.restore();
})

Related

Jest TestEnvironment - TypeError: Cannot add property next, object is not extensible

I want to test a node API using Jest. I am testing the routes and websockets. Testing the routes was no problem. I simply started the server using the setupFile option.
To test the websockets I wanted to pass the io object to the tests. This is not possible through the setupFile since the tests are running in their own context. Thus I changed to the testEnvironment option. My testEnvironment file is the following
const NodeEnvironment = require('jest-environment-node');
class CustomEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context);
this.setupServer();
}
async setup() {
await super.setup();
console.log('Setup Test Environment.');
this.global.io = this.io;
this.global.baseUrl = 'http://localhost:' + this.port;
}
async teardown() {
await super.teardown();
console.log('Teardown Test Environment.');
}
getVmContext() {
return super.getVmContext();
}
setupServer() {
// Code for starting the server and attaching the io object
this.port = portConfig.http;
this.io = io;
}
}
module.exports = CustomEnvironment;
This works and the io object is passed to the test. I have multiple test files for different parts of the API. Running those with the setupFile was no problem but now Jest is only able to run one file. All following test suites are failing with the following message
● Test suite failed to run
TypeError: Cannot add property next, object is not extensible
at Function.handle (node_modules/express/lib/router/index.js:160:12)
at Function.handle (node_modules/express/lib/application.js:174:10)
at new app (node_modules/express/lib/express.js:39:9)
I am not able to find any documentation on that error. I tried disabling some of the test files but it always fails after the first one, no matter which one it is.
The structure of the test files is the following if relevant:
const axios = require('axios');
describe('Test MODULE routes', () => {
const baseUrl = global.baseUrl;
const io = global.io;
const models = require('../../../models'); // sequelize models which are used in tests
describe('HTTP METHOD + ROUTE', () => {
test('ROUTE DESCRIPTION', async () => {
const response = await axios({
method: 'get',
url: baseUrl + 'ROUTE'
});
expect(response.status).toBe(200);
});
});
// different routes
});
I fixed the error. It had nothing to do with jest but with an module.exports invocation in the server setup which overwrote the export of the CustomEnvironment with an express server.

How to mock module which depends implicitly per test in Jest?

I have an integration test where I make actual DB calls to the MongoDB database. But in order to test whether the transaction is expired or not, I need to mock the DB for that particular test. There are many reasons for me to make the actual DB call, I'm mentioning the state just for the sake of this example.
Jest has jest.doMock function but that is helpful only when I wanted to import the function within the test but in my case, It's the DB function which I wanted to mock for that particular test when is getting called inside the express middleware.
There is another option to mock the entire ../db module but that will complicate the tests a lot in my actual project. It would be very easy for me if I can mock the DB call for a specific test and for rest all the tests it should make the real DB calls.
Is there a way to do it in Jest?
// a.ts
import express from "express"
import db from "../db";
const app = express()
app.get("/api/deduct-balance/:txn_id", (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
});
// a.test.ts
import db from "../db";
describe("mixed tests", () => {
test("should make REAL db calls", async () => {
await axios.get("/api/deduct-balance/123")
const txn = await db.findById("123");
expect(txn.state).toBe("DEDUCTED");
});
test("should use MOCKED value", async () => {
// need a way to mock the DB call so that I can return an expired transaction
// when I hit the API
const { data } = await axios.get("/api/deduct-balance/123")
expect(data).toBe({
error: {
message: "txn expired"
}
});
});
})
Integration tests are overkill for this scenario. Simple unit tests would suffice. They are fast to execute, test exactly one thing and you should have lots of them.
Because you're defining the handler as an anonymous function it's hard to unit test by default. So the first action is to make it easier to test by extracting it.
// deduct-balance-handlers.ts
export const deductBalanceByTransaction = async (req, res) => {
const txn = await db.findById(txn_id)
// return error message if txn expired
if (txn.exipre_at <= new Date()) {
return res.status(401).json({ error: "txn expired" });
}
// otherwise update the txn state
txn.state = "DEDUCTED";
await txn.save()
return res.status(200).json();
}
It will also makes the app configuration more clean.
// a.ts
import express from "express"
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
const app = express()
app.get("/api/deduct-balance/:txn_id", deductBalanceByTransaction);
Now it's easy to reuse the handler in your test without relying on the web framework or database.
// a.test.ts
import db from "../db";
import { deductBalanceByTransaction } from './deduct-balance-handlers';
jest.mock('../db');
describe("deduct-balance", () => {
test("Expired transaction should respond with 401 status", async () => {
const response = mockResponse();
deductBalanceByTransaction(request, response);
expect(response.status).toBe(401);
});
})
For simplicity's sake I left the part of creating a mock response and mocking the module out of the code. More can be learned about mocking here: https://jestjs.io/docs/en/manual-mocks

Mocking node_modules which return a function with Jest?

I am writing a typeScript program which hits an external API. In the process of writing tests for this program, I have been unable to correctly mock-out the dependency on the external API in a way that allows me to inspect the values passed to the API itself.
A simplified version of my code that hits the API is as follows:
const api = require("api-name")();
export class DataManager {
setup_api = async () => {
const email = "email#website.ext";
const password = "password";
try {
return api.login(email, password);
} catch (err) {
throw new Error("Failure to log in: " + err);
}
};
My test logic is as follows:
jest.mock("api-name", () => () => {
return {
login: jest.fn().mockImplementation(() => {
return "200 - OK. Log in successful.";
}),
};
});
import { DataManager } from "../../core/dataManager";
const api = require("api-name")();
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("200 - OK. Log in successful.");
expect(api.login).toHaveBeenCalledTimes(1);
});
});
What I find perplexing is that the test assertion which fails is only expect(api.login).toHaveBeenCalledTimes(1). Which means the API is being mocked, but I don't have access to the original mock. I think this is because the opening line of my test logic is replacing login with a NEW jest.fn() when called. Whether or not that's true, I don't know how to prevent it or to get access to the mock function-which I want to do because I am more concerned with the function being called with the correct values than it returning something specific.
I think my difficulty in mocking this library has to do with the way it's imported: const api = require("api-name")(); where I have to include an opening and closing parenthesis after the require statement. But I don't entirely know what that means, or what the implications of it are re:testing.
I came across an answer in this issue thread for ts-jest. Apparently, ts-jest does NOT "hoist" variables which follow the naming pattern mock*, as regular jest does. As a result, when you try to instantiate a named mock variable before using the factory parameter for jest.mock(), you get an error that you cannot access the mock variable before initialization.
Per the previously mentioned thread, the jest.doMock() method works in the same way as jest.mock(), save for the fact that it is not "hoisted" to the top of the file. Thus, you can create variables prior to mocking out the library.
Thus, a working solution is as follows:
const mockLogin = jest.fn().mockImplementation(() => {
return "Mock Login Method Called";
});
jest.doMock("api-name", () => () => {
return {
login: mockLogin,
};
});
import { DataManager } from "../../core/dataManager";
describe("DataManager.setup_api", () => {
it("should login to API with correct parameters", async () => {
//Arrange
let manager: DataManager = new DataManager();
//Act
const result = await manager.setup_api();
//Assert
expect(result).toEqual("Mock Login Method Called");
expect(mockLogin).toHaveBeenCalledWith("email#website.ext", "password");
});
});
Again, this is really only relevant when using ts-jest, as using babel to transform your jest typescript tests WILL support the correct hoisting behavior. This is subject to change in the future, with updates to ts-jest, but the jest.doMock() workaround seems good enough for the time being.

How to share a global variable between test files from a test in TestCafe?

I'm manually setting auth cookies for my login purpose and I would like to share the Auth token across my tests. The very first time I have to perform a login in a test and then I have to save the auth token in a variable and share it across the test files.
Here is the code snippet to explain what and how I'm trying to do:
loginTest.js :
let authToken = null;
fixture`Login test`
.page(inputData.url)
.beforeEach(async (t) => {
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
await t.navigateTo(inputData.url).then(await setCookie('AUTH_COOKIE_ID', authToken, nextMonth));
});
test
.before(async () => {
await loginPage.login(inputData.firstUserEmailId, inputData.firstUserPassword);
authToken = await getCookie('AUTH_COOKIE_ID');
})('Verify login test', async (t) => {
await loginPage.goToPeople(personName);
await t
.expect(loginPage.personName.exists)
.ok();
});
Now, after the test, I have the actual authToken (not null) and if I have to share the authToken variable across all my tests in all my files then how do I do? with this coding design I can share authToken in the same file (test suite). for example:
I have a file peopleTest.js :
fixture`People test`
.page(inputData.url)
.beforeEach(async (t) => {
const nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
await t.navigateTo(inputData.url).then(await setCookie('AUTH_COOKIE_ID', loginTest.authToken, nextMonth));
});
test('Verify people test', async (t) => {
await loginPage.goToPeople(personName);
await t
.expect(loginPage.personName.exists)
.ok();
});
In the above test, if I can do loginTest.authToken that would be great.
PS: In case, people are wondering why I'm doing setting cookie instead of using useRole. Just to let you know that the useRole didn't work in my setup as the application sets the cookie manually in my local env so I have to manually set the cookie as a login workaround.
Refer to the Extract Reusable Test Code receipt to find information on how you can share your test code between your test cases.
Also, you can use the fixture context object if you want to share your object only between tests of a particular fixture: Sharing Variables Between Fixture Hooks and Test Code.
For instance:
helper.js
var authToken = 111;
function setToken(x) {
authToken = x;
}
function getToken(x) {
return authToken;
}
export { setToken, getToken };
test.js
import { getToken, setToken } from './helper.js'
fixture("test1")
.page("http://localhost");
test('test1', async t => {
console.log('token: ' + getToken());
setToken(111);
});
test1.js
import { getToken } from './helper.js'
fixture("test2")
.page("http://localhost");
test('test2', async t => {
console.log('token2: ' + getToken());
});
See also:
import
export

Cannot read property of undefined thrown from running NPM and PACT

I'm trying to follow the PACT workshop example with some alternate data.
This may be more of a Javascript/Node question but I'm a but stumped, as a novice.
Given a consumer.spec.js file of:
const chai = require('chai');
const nock = require('nock');
const chaiAsPromised = require('chai-as-promised');
const expect = chai.expect;
const API_PORT = process.env.API_PORT || 9123;
chai.use(chaiAsPromised);
const API_HOST = `http://localhost:${API_PORT}`;
describe('Consumer', () => {
describe('when a call to the Provider is made', () => {
const clothingStatus = 'hello';
const {emailClothingOfferStatus} = require('../client');
it('can process the HTML payload from the provider', () => {
nock(API_HOST)
.get('/provider')
.query({validPermStatus:'hello'})
.reply(200, {
test:'NO',
validPermStatus: clothingStatus,
count: 1000,
});
const response = emailClothingOfferStatus(clothingStatus);
return expect(response.body.clothingStatus).to.eventually.equal('hello')
})
})
});
and a client .js file of:
const request = require('superagent');
const API_HOST = process.env.API_HOST || 'http://localhost';
const API_PORT = process.env.API_PORT || 9123;
const API_ENDPOINT = `${API_HOST}:${API_PORT}`;
// Fetch provider data
const emailClothingOfferStatus = emailPermChoice => {
let withEmailClothing = {};
const emailClothingGrantedRegex = 'hello';
if(emailPermChoice){
console.log(emailPermChoice);
withEmailClothing = {validPermStatus: emailPermChoice}
}
return request
.get(`${API_ENDPOINT}/provider`)
.query(withEmailClothing)
.then(
res => {
if (res.body.validPermStatus.match(emailClothingGrantedRegex)) {
return {
clothingStatus: (res.body.validPermStatus),
}
} else {
throw new Error('Could not verify email clothing offer status')
}
},
err => {
throw new Error(`Error from response: ${err.body}`)
}
)
};
module.exports = {
emailClothingOfferStatus,
};
and I have the following in my package.json scripts:
"test:consumer": "./node_modules/.bin/mocha --timeout 150000 pact/consumer/test/consumer.spec.js",
When I run npm run test:consumer, I get:
1) Consumer
when a call to the Provider is made
can process the HTML payload from the provider:
TypeError: Cannot read property 'clothingStatus' of undefined
at Context.it (pact/consumer/test/consumer.spec.js:29:35)
I'm sure it's something obvious but can anyone help?
Two things stand out to me as a problem:
The test above is a normal unit test designed to show how unit tests won't catch contract issues, and leads you into why Pact is useful (In case this wasn't clear). In short, it's not a Pact test at all - I can tell because it's using Nock, meaning the expected requests will never reach Pact. I can also tell because the Pact package doesn't appear to be imported. You want to model from this file https://github.com/DiUS/pact-workshop-js/blob/master/consumer/test/consumerPact.spec.js
The response value is a Promise, which means you can't do return expect(response.body.clothingStatus).to.eventually.equal('hello') because response is a promise, so body will be undefined and clothingStatus is not a property of that. The chai eventually API is useful for this sort of test, but as I understand, it has to work directly with a Promise - you could do expect(response).to... and then chai can go to work.
Your function emailClothingOfferStatus returns response.then() which is a promise and not an actual response.
Therefore response.body is undefined.
You should be able to test the result like this:
const response = emailClothingOfferStatus(clothingStatus);
response.then((res) => {
expect(res.body.clothingStatus).to.eventually.equal('hello')
})

Categories

Resources