How to test failed request with axios / jest - javascript

I've created a very small app that calculates the total price to pay for certain crypto if you pass in the coin and quantity. I want to test for errors but I am always getting "Received promise resolved instead of rejected". I believe this is due to the fact that if the url is wrong axios still resolves the promise.
The second problem I have is that I have tried testing if the url is correct but I have an issue that headers are part of the url and I can't see to figure out how to test just the url path without headers.
Those are tests 2 and 3 (Test number 1 is working)
// getTotalPrice.js
const axios = require("axios");
let symbol = process.argv[2];
let quantity = process.argv[3];
const API = `https://rest.coinapi.io/v1/exchangerate`;
const headers = {headers:{"X-CoinAPI-Key": "MY TOKEN"},};
const getTotalPrice = async (symbol = "BTC", quantity = 1) => {
try {
let res = await axios.get(`${API}/${symbol}/USD`, headers);
let data = res.data;
return Math.round(data.rate * quantity * 100) / 100;
} catch(err) {
console.log(err)
}
};
getTotalPrice(symbol, quantity);
module.exports = {
getTotalPrice,
API
};
// getTotalPrice.test.js
const { get, getError } = require("axios");
const { getTotalPrice } = require("../getTotalPrice");
describe("getTotalPrice", () => {
afterEach(() => {
get.mockClear();
});
it("fetches data successfully from an api", async () => {
const res = {
data: {
rate: 34000,
},
};
get.mockImplementation(() => Promise.resolve(res));
await expect(getTotalPrice()).resolves.toEqual(res.data.rate);
});
it("throws an error when incorrect data is passed", async () => {
const errorMessage = "Wrong inputs passed in";
getError.mockImplementationOnce(() => Promise.reject({}));
await expect(getTotalPrice()).rejects.toThrowError();
});
it("uses correct url", async () => {
const data = {
data: {
rate: 2000,
},
};
get.mockImplementationOnce(() => Promise.resolve(data));
await getTotalPrice("ETH");
expect(get).toHaveBeenCalledWith(
`${API}/ETH/USD`
);
});
});
// axios.js (in __mocks__)
module.exports = {
get: jest.fn(() => Promise.resolve({ data: {} })),
getError: jest.fn(() => Promise.reject()),
};
To recap, test 1 passes, test 2 fails with "Received promise resolved instead of rejected" and test 3 fails as I am getting headers as well not just url.

You should mock axios.get() with resolved/rejected value. Since you use the try...catch... statement, the error is thrown by axios.get() method will be caught, and you didn't rethrow any errors. So the assertion of test case 2 will not match toThrowError. Instead, you can use jest.spyOn() to add spy to console.log, and assert it to be called with the mocked Error.
Here is an solution only using jest.mock('axios') to mock axios module without __mocks__/axios.js file.
E.g.
getTotalPrice.js:
const axios = require('axios');
let symbol = process.argv[2];
let quantity = process.argv[3];
const API = `https://rest.coinapi.io/v1/exchangerate`;
const headers = { headers: { 'X-CoinAPI-Key': 'MY TOKEN' } };
const getTotalPrice = async (symbol = 'BTC', quantity = 1) => {
try {
let res = await axios.get(`${API}/${symbol}/USD`, headers);
let data = res.data;
return Math.round(data.rate * quantity * 100) / 100;
} catch (err) {
console.log(err);
}
};
module.exports = { getTotalPrice, API };
getTotalPrice.test.js:
const axios = require('axios');
const { getTotalPrice, API } = require('./getTotalPrice');
jest.mock('axios');
describe('getTotalPrice', () => {
it('fetches data successfully from an api', async () => {
const res = { data: { rate: 34000 } };
axios.get.mockResolvedValueOnce(res);
await expect(getTotalPrice()).resolves.toEqual(res.data.rate);
});
it('throws an error when incorrect data is passed', async () => {
const logSpy = jest.spyOn(console, 'log');
const err = new Error('Wrong inputs passed in');
axios.get.mockRejectedValueOnce(err);
await getTotalPrice();
expect(logSpy).toBeCalledWith(err);
});
it('uses correct url', async () => {
const res = { data: { rate: 2000 } };
axios.get.mockResolvedValueOnce(res);
await getTotalPrice('ETH');
expect(axios.get).toHaveBeenCalledWith(`${API}/ETH/USD`, { headers: { 'X-CoinAPI-Key': 'MY TOKEN' } });
});
});
test result:
PASS examples/68200193/getTotalPrice.test.js (7.527 s)
getTotalPrice
✓ fetches data successfully from an api (3 ms)
✓ throws an error when incorrect data is passed (14 ms)
✓ uses correct url (1 ms)
console.log
Error: Wrong inputs passed in
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:14:17
at Generator.next (<anonymous>)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:8:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:4:12)
at Object.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/examples/68200193/getTotalPrice.test.js:12:66)
at Object.asyncJestTest (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:75:41
at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
at Generator.throw (<anonymous>)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
getTotalPrice.js | 100 | 100 | 100 | 100 |
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 8.034 s

A colleague recommended this to me, today, for mocking axios. I haven't tried it out, yet, though.
API mocking of the next generation
Mock by intercepting requests on the network level. Seamlessly reuse the same mock definition for testing, development, and debugging.
https://mswjs.io/

Related

Can we mock function which is calling axios function inside without mocking axios in JEST for nodejs code

Please refer below code.
Server.js code which is calling an API enclosed in loadUser function and called by fullNameGen function to create full name
const axios = require('axios');
function loadUser(){
return new Promise((resolve,reject)=>{
console.log("Load user called");
axios.get('https://reqres.in/api/users/2').then((response)=>{
resolve(response);
}).catch((err)=>{
reject(err);
})
})
}
function fullNameGen(){
return new Promise((res,rej)=>{
loadUser().then(({data:info})=>{
console.log(info.data);
res(info.data.first_name+" "+info.data.last_name);
}).catch((err)=>{
console.log(err.message);
rej(err.message);
})
})
}
module.exports={
fullNameGen:fullNameGen,
loadUser:loadUser
}
And in server.test.js file I am trying to mock loadUser function not the axios directly.
But the test still calling axios with the API .
server.test.js
const axios = require('axios');
const server = require('./server');
test("Check full Name generation",async()=>{
server.loadUser=jest.fn();
server.loadUser.mockResolvedValue={
data:{
data:{
first_name:"rav",
last_name:"shekhar"
}}
};
const result = await server.fullNameGen();
expect(result).toBe("ravi shekhar");
expect(server.fullNameGen).toBeCalledTimes(1)
})
Please tell me why this test is giving error
expect(received).toBe(expected) // Object.is equality
Expected: "ravi shekhar"
Received: "Janet Weaver"
28 | const result = await server.fullNameGen();
29 |
> 30 | expect(result).toBe("ravi shekhar");
| ^
31 | expect(server.fullNameGen).toBeCalledTimes(1)
32 | })
at Object.<anonymous> (server.test.js:30:20)

Why java script fs.readFileSync is not getting mocked?

i have a fs.readFileSync function that i need to mock using jest.
i have tried the below code.
my original file which i want to test.
const fs = require('fs');
const read_file = (path) => {
try {
const data = fs.readFileSync(path, 'utf8');
return data;
} catch (err) {
console.error('Error in read_file', err);
throw err;
}
};
const getSecret = secretName => {
try {
return read_file(`/etc/secrets/${secretName}.txt`);
} catch (err){
throw err;
}
};
const secretConfig = {
kafka_keystore_password: getSecret('kafka_keystore_password')
};
module.exports = secretConfig;
here is my test case
const secret = require('./secret');
let fs = require('fs')
jest.mock('fs');
describe('secret read files', () => {
afterEach(jest.restoreAllMocks);
it('should read secret from file', () => {
//fs.readFileSync.mockReturnValue("randomPrivateKey");
fs.readFileSync.mockResolvedValue("randomPrivateKey")
const secretMessage = secret.kafka_keystore_password;
//expect(fs.readFileSync).toHaveBeenCalled();
expect(secretMessage).toEqual('randomPrivateKey');
})
})
describe('getSecretsFromFile', () => {
const secret = 'secret';
const secrets = { mySecret: secret };
afterEach(jest.restoreAllMocks);
it('returns secrets if file is present', () => {
fs.existsSync.mockReturnValue(true);
fs.readFileSync.mockReturnValue('randomPrivateKey');
const secretMessage = secret.kafka_keystore_password;
expect(secretMessage).toEqual('randomPrivateKey');
});
});
and i get the following error.
FAIL src/config/secret.test.js ● secret read files › should read
secret from file
expect(received).toEqual(expected) // deep equality
Expected: "randomPrivateKey"
Received: undefined
15 | const secretMessage = secret.kafka_keystore_password;
16 | //expect(fs.readFileSync).toHaveBeenCalled();
> 17 | expect(secretMessage).toEqual('randomPrivateKey');
| ^
18 |
19 | })
20 |
at Object.<anonymous> (src/config/secret.test.js:17:27)
● getSecretsFromFile › returns secrets if file is present
expect(received).toEqual(expected) // deep equality
Expected: "randomPrivateKey"
Received: undefined
32 |
33 | const secretMessage = secret.kafka_keystore_password;
> 34 | expect(secretMessage).toEqual('randomPrivateKey');
| ^
35 | });
36 | });
37 |
at Object.<anonymous> (src/config/secret.test.js:34:29)
help me fix this .
try:
const fs = {
readFileSync: jest.fn(() => ({ message: 'Test'}))
}
to mock the fs readFileSync message.
But I don't see how this test is ever going to pass because you're returning value "Test"
but checking for randomPrivateKey
expect(secretMessage).toEqual('randomPrivateKey');
Without seeing what's going on inside the code it's difficult to say but I am assuming you might want to change that line to:
expect(secretMessage).toEqual('test');
As test is the value your mocking alternatively return randomPrivateKey from the mock.
I'm having to make a load of assumptions here because I don't know-how
secret.kafka_keystore_password is calculated. I'm assuming it just returns the whatever the fs.readFileSync returns.7

Unable to use promise returned by Jest beforeAll

I am trying to write unit tests for my mongodb queries using mongodb-memory-server, mongodb and Jest. I am trying to setup the db from beforeAll and hoping to use the promises returned into my test.
Here is my code which I have developed using the Jest documentation provided in their setup and teardown section:
dbOperations.js
const { MongoMemoryServer } = require('mongodb-memory-server')
const MongoClient = require('mongodb').MongoClient
const testDocument = {
"name" : "test"
}
let client = null
let mongoServer = null
module.exports.initdb = async () => {
mongoServer = new MongoMemoryServer()
const mongoUri = await mongoServer.getUri()
console.log("init db -----------------")
try {
client = await MongoClient.connect(mongoUri, {
useUnifiedTopology: true,
})
const test_db = await client.db(await mongoServer.getDbName()) //generates random DB name.
const test_collection = await test_db.collection("test")
console.log("in function------", await test_db.stats()) // i can see these in output
await test_collection.insertOne(testDocument) // load test data
return {
test_db,
test_collection
}
}
catch(e) {
console.log("Setup Error", e)
}
}
module.exports.teardown = () => {
if (client) {
console.log("closing mongo client")
client.close()
}
if (mongoServer) {
console.log("closing mongo server")
mongoServer.stop()
}
}
And my test.js
const {initdb, teardown} = require('../testUtils/dbOperations')
describe('MongoDB Test Suite', () => {
beforeAll ( () => {
return initdb()
})
afterAll ( () => {
return teardown()
})
test('test connection and document setup ', async () => {
let docCount = await test_collection.countDocuments({})
expect(test_db).toBeDefined()
expect(docCount).toBe(1);
})
})
And my console output :
MongoDB Test Suite
✕ test connection and document setup (3 ms)
● MongoDB Test Suite › test connection and document setup
ReferenceError: test_collection is not defined
15 |
16 | test('test connection and document setup ', async () => {
> 17 | let docCount = await test_collection.countDocuments({})
| ^
18 | expect(test_db).toBeDefined()
19 | expect(docCount).toBe(1);
20 | })
at Object.test (tests/unit-tests/test.js:17:24)
console.log
init db -----------------
at Object.<anonymous>.module.exports.initdb (tests/testUtils/dbOperations.js:14:11)
console.log
in function------ { db: '5129a396-09f4-43ad-8ceb-b843932a3956',
collections: 0,
views: 0,
objects: 0,
avgObjSize: 0,
dataSize: 0,
storageSize: 0,
numExtents: 0,
indexes: 0,
indexSize: 0,
fileSize: 0,
ok: 1 }
at Object.<anonymous>.module.exports.initdb (tests/testUtils/dbOperations.js:21:15)
console.log
Setup Error ReferenceError: test_db is not defined
at Object.<anonymous>.module.exports.initdb (/Users/souparno/Code/aws-lambdas/tests/testUtils/dbOperations.js:24:9)
at process._tickCallback (internal/process/next_tick.js:68:7)
at Object.<anonymous>.module.exports.initdb (tests/testUtils/dbOperations.js:29:15)
console.log
closing mongo client
at teardown (tests/testUtils/dbOperations.js:35:15)
console.log
closing mongo server
at teardown (tests/testUtils/dbOperations.js:39:15)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 0.971 s, estimated 1 s
Everything works normally if I put all my code in a single module. Somehow, when splitting setup functions in a new module, I am unable to utilize the Promise returned by initdb called inside my beforeAll block.
You need to assign the return value of initdb to something:
describe('MongoDB Test Suite', () => {
let test_collection
beforeAll ( async () => {
const result = await initdb()
test_collection = result.test_collection
})
...

Mocking function object with jest

I currently have an object that is used to interact with my API api.js:
export var Auth = (function () {
var Login = async function(username, password) {
//Async login code for interacting with the API
};
return {
Login: Login
}
});
And this object is imported inside another file, login.js:
import * as API from './api';
export var LoginRequestHandler = function() {
//processess user input, then calls:
try {
await API.Auth().Login(username, password);
} catch(e) {
throw new Error(e);
}
This is my jest test:
import * as API from '../api';
import * as User from '../user';
jest.mock('../api');
const spy = jest.spyOn(API.Auth(), 'Login');
User.LoginRequestHandler().then(() => {
expect(spy).toHaveBeenLastCalledWith('theUsername', 'thePassword');
}).catch(error => console.log(error));
This is my mock file, __mock__/api.js:
export var Auth = (function () {
var Login = async function(username, password) {
return Promise.resolve(true);
};
return {
Login: Login
}
});
I retrieve theUsername and thePassword through document.getElementId() in the LoginRequestHandler and create my own DOM for the test above.
Adding console.log(username) in the LoginRequestHandler reveals that it is being called and is able to get the right values. Furthermore, adding a console.log(username) in API.Auth().Login also reveals that it is getting the right values as well. However, when I look at my test logs, I see: Number of calls: 0 for the mock function and the test results in errors.
I assume that I am trying to spy on the wrong function, and is there anyway that I can fix this?
Every time you call API.Auth(), it will return a new object which has a Login method. So, the object created in LoginRequestHandler function and created by jest.spyOn(API.Auth(), 'Login') statement in your test case are different. The spy is only added to the later one. The Login method in LoginRequestHandler function is not spied.
So, here I am going to use jest.mock() to mock the api.js module without putting mocked object to __mocks__ directory. E.g.
api.js:
export var Auth = function () {
var Login = async function (username, password) {};
return {
Login: Login,
};
};
user.js:
import * as API from './api';
export var LoginRequestHandler = async function () {
const username = 'theUsername';
const password = 'thePassword';
try {
await API.Auth().Login(username, password);
} catch (e) {
throw new Error(e);
}
};
user.test.js:
import * as API from './api';
import * as User from './user';
jest.mock('./api', () => {
const auth = { Login: jest.fn() };
return {
Auth: jest.fn(() => auth),
};
});
describe('61643983', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should login', () => {
expect.assertions(2);
return User.LoginRequestHandler().then(() => {
expect(API.Auth).toBeCalledTimes(1);
expect(API.Auth().Login).toHaveBeenLastCalledWith('theUsername', 'thePassword');
});
});
it('should throw error', () => {
expect.assertions(4);
const mError = new Error('user not found');
API.Auth().Login.mockRejectedValueOnce(mError);
return User.LoginRequestHandler().catch((e) => {
expect(API.Auth).toBeCalled();
expect(API.Auth().Login).toHaveBeenLastCalledWith('theUsername', 'thePassword');
expect(e).toBeInstanceOf(Error);
expect(e.message).toMatch(/user not found/);
});
});
});
unit test results with 100% coverage:
PASS stackoverflow/61643983/user.test.js (11.507s)
61643983
✓ should login (5ms)
✓ should throw error (3ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
user.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 13.023s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61643983

Using Jest.fn() to see if s3.upload function was called...what am I doing wrong?

I am a bit new to testing and I have been stuck on this issue for quite some time. So I am trying to test a s3.upload() function to see if it called, not to see if it actually uploads the object. The only constraint is that I cannot use any npm packages to mock out the functionality of the s3 bucket.
I was trying to follow this tutorial (How to mock a function inside another function (which I am testing) using sinon?) that uses sinon as a stub, but instead use jest instead. Any help or guidance with issue is appreciated.
// function.js
const uploadToS3 = (params) => {
const response = s3.upload(params).promise();
return response;
}
// functions.test.js
describe("Lambda Handler Function", () => {
test('To test to see if the uploadToS3 function was called', () => {
const sampleParam = {
Bucket: 'BucketName',
Key: 'BucketKey.zip',
Body: 'SGVsbG8sIFdvcmxk'
}
expect(uploadToS3(sampleParam).response).toBeCalled()
})
})
You can use jest.mock(moduleName, factory, options) to mock aws-sdk.
E.g.
function.js:
import AWS from 'aws-sdk';
const s3 = new AWS.S3();
const uploadToS3 = async (params) => {
const response = await s3.upload(params).promise();
return response;
};
export { uploadToS3 };
function.test.js:
import { uploadToS3 } from './function';
import AWSMock from 'aws-sdk';
jest.mock('aws-sdk', () => {
const mS3 = { upload: jest.fn().mockReturnThis(), promise: jest.fn() };
return { S3: jest.fn(() => mS3) };
});
describe('60970919', () => {
it('should pass', async () => {
const mS3 = new AWSMock.S3();
const mResponse = { Bucket: 'xxx' };
mS3.upload({}).promise.mockResolvedValueOnce(mResponse);
const actual = await uploadToS3({});
expect(actual).toEqual(mResponse);
expect(mS3.upload).toBeCalledWith({});
expect(mS3.upload().promise).toBeCalled();
});
});
unit test results with 100% coverage:
PASS stackoverflow/60970919/function.test.js (13.818s)
60970919
✓ should pass (9ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
function.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 15.486s
source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60970919

Categories

Resources