How to spy on a nested function in Jest - javascript

I have a function that I am trying to test. I want to test that lambda.invoke is called with the correct arguments, however my test keeps saying that .invoke is never called. I think I am spying incorrectly. How can I spy on the .invoke and the .promise function?
Here is the function:
/* Amplify Params - DO NOT EDIT
ENV
FUNCTION_EMAIL_NAME
STRIPE_ENDPOINT_SECRET
REGION
Amplify Params - DO NOT EDIT */
const stripe = require('stripe');
const AWS = require('aws-sdk');
/**
* #type {import('#types/aws-lambda').APIGatewayProxyHandler}
*/
exports.handler = async (event) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
const lambda = new AWS.Lambda({ region: process.env.REGION });
const sig = event.headers['Stripe-Signature'];
let stripeEvent;
try {
stripeEvent = stripe.webhooks.constructEvent(
event.body,
sig,
process.env.STRIPE_ENDPOINT_SECRET
);
} catch (err) {
return {
statusCode: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
},
body: JSON.stringify(`Webhook Error: ${err.message}`),
};
}
// Handle the event
switch (stripeEvent.type) {
case 'payment_intent.succeeded':
// eslint-disable-next-line no-case-declarations
const paymentIntent = stripeEvent.data.object;
// Then define and call a function to handle the stripeEvent payment_intent.succeeded
try {
const result = await lambda
.invoke({
FunctionName: process.env.FUNCTION_EMAIL_NAME,
Payload: JSON.stringify({
...event,
pathParameters: {
userID: paymentIntent.metadata.userID,
},
body: JSON.stringify({
user: {
emailAddress: paymentIntent.metadata.email,
},
}),
}),
})
.promise();
if (result.StatusCode === 200) {
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
},
body: JSON.stringify('Email successfully sent!'),
};
}
} catch (err) {
console.log('Email failed!', err);
}
break;
// ... handle other stripeEvent types
default:
console.log(`Unhandled stripeEvent type ${stripeEvent.type}`);
}
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
},
body: JSON.stringify('Hello from Lambda!'),
};
};
And here is the failing test:
const AWS = require('aws-sdk');
const stripe = require('stripe');
const { handler } = require('./index');
const event = require('./event.json');
jest.mock('stripe');
jest.mock('aws-sdk');
describe('handler', () => {
const { env } = process;
beforeEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
jest.clearAllMocks();
jest.resetModules();
process.env = {
...env,
REGION: 'us-east-1',
FUNCTION_EMAIL_NAME: 'example_function_name',
STRIPE_ENDPOINT_SECRET: 'stripe_test_key',
};
});
afterEach(() => {
process.env = env;
});
it('should call the email lambda if payment intent succeeded', async () => {
const lambdaSpy = jest.spyOn(AWS, 'Lambda');
const stripeSpy = jest.spyOn(stripe.webhooks, 'constructEvent');
const test = jest.fn();
stripeSpy.mockReturnValue({
type: 'succeeded',
});
lambdaSpy.mockReturnValue({ invoke: test, promise: jest.fn() });
await handler(event);
expect(test).toHaveBeenCalled();
});
});

Related

TypeError: axios.post(...).then is not a function

I have following Node.js script.
const axios = require('axios');
const https = require('https');
const postRequest = (url, data) => {
gLogger.debug('postRequest started');
// try {
const headers = {
'Content-Type': 'application/json',
'Content-Length': JSON.stringify(data).length,
};
const load = {
headers,
httpsAgent: agent,
};
gLogger.debug(`postRequest load: ${JSON.stringify(load)}`);
const result = axios.post(url, data, load).then((response) => {
return result;
})
.catch((error) => {
return error;
});
};
And this is for unit test:
const axios = require('axios');
const personalRecords = {
data: { peopleDomain: { paom: { data: { persons: [], delta: 1, recordsFetched: 10 } } } },
};
const tockenData = {
data: {
access_token: 'access_token',
expires_in: 1000,
},
};
// jest.useFakeTimers();
jest.setTimeout(8000);
jest.mock('axios', () => ({
post: jest.fn().mockReturnValue(tockenData),
get: jest.fn().mockReturnValue(personalRecords),
defaults: jest.fn().mockReturnValue(),
}));
The problem when I am running unit test yarn test, I keep getting the following error:
TypeError: axios.post(...).then is not a function.
What is the problem and how to fix it?
This is because you mock post function to be a function that returns a value instead of a promise. Remember post returns promise
This is the line that causes trouble:
post: jest.fn().mockReturnValue(tockenData),
To mock axios, there is an answer here:
How do I test axios in Jest?

Firestore getSignedUrl() give The caller does not have permission at Gaxios

I got following error at file.getSignedUrl. I have other function to copy the file and create new file on Cloud Storage. Why this function need permission and where do I need to set?
Error: The caller does not have permission at Gaxios._request (/layers/google.nodejs.yarn/yarn_modules/node_modules/gaxios/build/src/gaxios.js:129:23) at runMicrotasks () at processTicksAndRejections (node:internal/process/task_queues:96:5) at async Compute.requestAsync (/layers/google.nodejs.yarn/yarn_modules/node_modules/google-auth-library/build/src/auth/oauth2client.js:368:18) at async GoogleAuth.signBlob (/layers/google.nodejs.yarn/yarn_modules/node_modules/google-auth-library/build/src/auth/googleauth.js:662:21) at async sign (/layers/google.nodejs.yarn/yarn_modules/node_modules/#google-cloud/storage/build/src/signer.js:103:35) { name: 'SigningError' }
const functions = require("firebase-functions");
const axios = require("axios");
const { Storage } = require("#google-cloud/storage");
const storage = new Storage();
// Don't forget to replace with your bucket name
const bucket = storage.bucket("projectid.appspot.com");
async function getAlbums() {
const endpoint = "https://api.mydomain.com/graphql";
const headers = {
"content-type": "application/json",
};
const graphqlQuery = {
query: `query Albums {
albums {
id
album_cover
}
}`,
};
const response = await axios({
url: endpoint,
method: "post",
headers: headers,
data: graphqlQuery,
});
if (response.errors) {
functions.logger.error("API ERROR : ", response.errors); // errors if any
} else {
return response.data.data.albums;
}
}
async function updateUrl(id, url) {
const endpoint = "https://api.mydomain.com/graphql";
const headers = {
"content-type": "application/json",
};
const graphqlQuery = {
query: `mutation UpdateAlbum($data: AlbumUpdateInput!, $where:
AlbumWhereUniqueInput!) {
updateAlbum(data: $data, where: $where) {
id
}
}`,
variables: {
data: {
album_cover: {
set: url,
},
},
where: {
id: id,
},
},
};
const response = await axios({
url: endpoint,
method: "post",
headers: headers,
data: graphqlQuery,
});
if (response.errors) {
functions.logger.error("API ERROR : ", response.errors); // errors if any
} else {
return response.data.data.album;
}
}
const triggerBucketEvent = async () => {
const config = {
action: "read",
expires: "03-17-2025",
};
const albums = await getAlbums();
albums.map((album) => {
const resizedFileName = album.id + "_300x200.webp";
const filePath = "images/albums/thumbs/" + resizedFileName;
const file = bucket.file(filePath);
functions.logger.info(file.name);
file.getSignedUrl(config, function (err, url) {
if (err) {
functions.logger.error(err);
return;
} else {
functions.logger.info(
`The signed url for ${resizedFileName} is ${url}.`
);
updateUrl(album.id, url);
}
} );
});
};
exports.updateResizedImageUrl = functions.https.onRequest(async () => {
await triggerBucketEvent();
});
I need to add Service Account Token Creator role for App Engine default service account.

How to mock nested function in Jest?

I am getting this error
Cannot find module 'httpsGet' from 'functions/getSecureString.test.js'
httpsGet() is my own function, and is at the button of getSecureString.js, and called by getSecureString(). httpsGet() uses the https module to get content from a website that requires client side certificates.
Question
I am trying to mock httpsGet() and I am guessing the problem I have is because it isn't included with require() and hence jest.mock('httpsGet') fails.
Can anyone figure out if that is the case, and how I should fix it?
Live example at: https://repl.it/#SandraSchlichti/jest-playground-4
getSecureString.test.js
const getStatusCode = require('./getSectureString');
jest.mock('httpsGet');
describe("getSecureString ", () => {
describe('when httpsGet returns expected statusCode and body includes expected string', () => {
let result;
beforeAll(async () => {
httpsGet.mockResolvedValue({
statusCode: 200,
body: 'xyz secret_string xyz'
})
result = await getSecureString({
hostname: 'encrypted.google.com',
path: '/',
string: 'secret_string',
statusCode: 200,
aftaleId: 1234,
certFile: 1234,
keyFile: 1234,
timeout: 1000,
})
});
it('should return 1', () => {
expect(result).toEqual(1)
})
});
describe('when httpsGet returns expected statusCode and body includes expected string', () => {
let result;
beforeAll(async () => {
httpsGet.mockResolvedValue({
statusCode: 200,
body: 'xyz secret_string xyz'
})
result = await getSecureString({
hostname: 'encrypted.google.com',
path: '/',
string: 'not_secret_string',
statusCode: 201,
aftaleId: 1234,
certFile: 1234,
keyFile: 1234,
timeout: 1000,
})
});
it('should return 0', () => {
expect(result).toEqual(0)
})
});
describe("when an exception is thrown", () => {
let result;
beforeAll(async () => {
// mockRejected value returns rejected promise
// which will be handled by the try/catch
httpsGet.mockRejectedValue({
statusCode: 200,
body: 'xyz secret_string xyz'
})
result = await getSecureString();
})
it('should return -1', () => {
expect(result).toEqual(-1)
})
});
});
getSecureString.js
const fs = require('fs');
const https = require('https');
var uuid = require('uuid');
const {v4: uuidv4} = require('uuid');
module.exports = async function getSecureString(options) {
options = options || {};
options.hostname = options.hostname || {};
options.path = options.path || '/';
options.string = options.string || {};
options.statusCode = options.statusCode || {};
options.aftaleId = options.aftaleId || {};
options.certFile = options.certFile || {};
options.keyFile = options.keyFile || {};
options.timeout = options.timeout || 0;
const opt = {
hostname: options.hostname,
port: 443,
path: options.path,
method: 'GET',
cert: fs.readFileSync(options.certFile),
key: fs.readFileSync(options.keyFile),
headers: {'AID': options.aftaleId
},
};
opt.agent = new https.Agent(opt);
try {
const r = await httpsGet(opt, options.timeout);
return (r.statusCode === options.statusCode && r.body.includes(options.string)) ? 1 : 0;
} catch (error) {
console.error(error);
}
};
function httpsGet(opts, timeout) {
return new Promise((resolve, reject) => {
const req = https.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data.toString();
});
res.on('end', () => {
resolve({body, statusCode: res.statusCode});
});
});
req.setTimeout(timeout, function() {
req.destroy('error');
});
req.on('error', (e) => {
console.error(e);
reject(e);
});
});
};
A function that is used in the same module it was declared cannot be spied mocked, unless it's consistently as a method of some object, which is cumbersome and incompatible with ES modules:
module.exports.httpsGet = ...
...
module.exports.httpsGet(...);
Otherwise a function should be moved to another module that can mocked, or should be tested as is. In this case underlying API (https.get) can be mocked instead.

How can I mock functions in a imported module using Jest?

I have a module called authProvider.js which I want to mock when I'm testing one of my functions in api.js.
I have set "automock": true in my jest config.
This is my structure
src
|-auth
| |-__mocks__
| | |-authProvider.js
| |-authProvider.js
|-utils
|-api.js
|-api.test.js
This is what I have tried so far but only having success with the first test case. I'm not sure how to set up the mocking in the second test case...
api.test.js
import { mockAuthProvider } from "../auth/__mocks__/authProvider";
import { getDefaultHeaders, getValueIndexByColumnName } from './api';
describe('API utils', () => {
describe('getDefaultHeaders', () => {
it('Not authenticated', async () => {
expect(await getDefaultHeaders()).toEqual({
'Content-Type': 'application/json'
});
});
it('Authenticated', async () => {
mockAuthProvider.getAccount.mockImplementationOnce(() =>
Promise.resolve({user: 'test'})
);
const headers = await getDefaultHeaders();
expect(mockAuthProvider.getAccount).toBeCalled();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
});
});
api.js
import { authProvider } from '../auth/authProvider';
import settings from '../settings';
export async function getDefaultHeaders() {
const account = await authProvider.getAccount();
const authenticationParameters = {
scopes: ['api://' + settings.AD_CLIENT_ID + '/login'],
redirectUri: window.location.origin + '/auth.html'
};
let token;
if (account) {
try {
token = await authProvider.acquireTokenSilent(authenticationParameters);
} catch (error) {
token = await authProvider.acquireTokenPopup(authenticationParameters);
}
}
if (token) {
return {
Authorization: `Bearer ${token.accessToken}`,
'Content-Type': 'application/json'
}
}
return {
'Content-Type': 'application/json'
}
}
__ mocks __/authProvider.js
const mockAuthProvider = {
getAccount: jest.fn(),
acquireTokenSilent: jest.fn(),
acquireTokenPopup: jest.fn()
};
module.exports = {
mockAuthProvider
};
Error message
Expected number of calls: >= 1
Received number of calls: 0
18 | const headers = await getDefaultHeaders();
19 |
> 20 | expect(mockAuthProvider.getAccount).toBeCalled();
| ^
UPDATE
I added a file to mock the whole module that exports the auth provider, but still not the best way to solve it I think. I'm having difficulties using it in multiple test cases since I need to specify the return values in a specific order.
Is there a better way to solve this issue?
__ mocks __/react-aad-msal.js
import React from 'react';
const errorObj = {
message: 'Some error'
};
export const mockGetAccount = jest.fn()
.mockReturnValueOnce(null) // Not authenticated
.mockReturnValueOnce({user: 'test'}) // Authenticated silent
.mockReturnValueOnce({user: 'test'}); // Authenticated popup
export const mockAcquireTokenSilent = jest.fn()
.mockReturnValueOnce({accessToken: 'abc123'}) // Authenticated silent
.mockRejectedValueOnce(errorObj); // Authenticated popup
export const mockAcquireTokenPopup = jest.fn()
.mockReturnValueOnce({accessToken: 'abc123'}); // Authenticated popup
export const MsalAuthProvider = jest.fn(() => ({
getAccount: mockGetAccount,
acquireTokenSilent: mockAcquireTokenSilent,
acquireTokenPopup: mockAcquireTokenPopup
}));
export const AuthenticationState = {
Authenticated: 'Authenticated',
Unauthenticated: 'Unauthenticated'
};
export const LoginType = {
Popup: 'popup'
};
export const AuthenticationActions = {
Initializing: 'Initializing',
Initialized: 'Initialized',
AcquiredIdTokenSuccess: 'AcquiredIdTokenSuccess',
AcquiredAccessTokenSuccess: 'AcquiredAccessTokenSuccess',
AcquiredAccessTokenError: 'AcquiredAccessTokenError',
LoginSuccess: 'LoginSuccess',
LoginError: 'LoginError',
AcquiredIdTokenError: 'AcquiredIdTokenError',
LogoutSucc: 'LogoutSucc',
AuthenticatedStateChanged: 'AuthenticatedStateChanged'
};
export const AzureAD = ({children}) => <div>{children}</div>;
The new api.test.js looks like this, note that the order of the tests now is important since the return values from the mock are in a fixed order.
import { getDefaultHeaders, axiosCreate, getValueIndexByColumnName } from './api';
describe('API utils', () => {
describe('getDefaultHeaders', () => {
it('Not authenticated', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
'Content-Type': 'application/json'
});
});
it('Authenticated silent', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
it('Authenticated popup', async () => {
const headers = await getDefaultHeaders();
expect(headers).toEqual({
Authorization: 'Bearer abc123',
'Content-Type': 'application/json'
});
});
});
describe('axiosCreate', () => {
it('Create axios API base', () => {
expect(axiosCreate()).toBeTruthy();
});
});
describe('getValueIndexByColumnName', () => {
it('Invalid input data', () => {
expect(getValueIndexByColumnName([], null)).toEqual(null);
expect(getValueIndexByColumnName(['column1'], null)).toEqual(-1);
});
it('Valid input data', () => {
expect(getValueIndexByColumnName(['column1'], 'column')).toEqual(-1);
expect(getValueIndexByColumnName(['column1'], 'column1')).toEqual(0);
expect(getValueIndexByColumnName(['column1', 'column2', 'column3'], 'column2')).toEqual(1);
});
});
});

How can I do Jest API test for this code?

I have tried several ways for mocking this unit of my code but still, it doesn't work. I'm using create-react-app and jest for testing.
I have a function in admin adminSignup.js for sending data to my server(Node.js and Mongoose) for creating account:
/* eslint-disable no-undef */
function signup(user, cb) {
return fetch(`signup`, {
headers: {"Content-Type": "application/json"},
method: "POST",
body:JSON.stringify({
username: user.username,
email: user.email,
password: user.password,
picode: user.pincode,
building: user.building,
city: user.city,
state: user.state
}),
})
.then(checkStatus)
.then(parseJSON)
.then(cb)
.catch(err => console.log(err));
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(`HTTP Error ${response.statusText}`);
error.status = response.statusText;
error.response = response;
console.log(error); // eslint-disable-line no-console
throw error;
}
function parseJSON(response) {
return response.json();
}
const adminSignup = { signup };
export default adminSignup;
and I have called this in my component(RegisterPage.jsx) :
adminSignup.signup( user, response => {
this.setState({response: response});
console.log(response);
});
Now I want to write a mock for my signup call(adminSignup.js). But just wonder how can I do this?
I have tried Jest Fetch Mock for mock testing(it doesnt need to create mock file) and it's working but I'm not quite sure is it correct or no :
describe('testing api', () => {
beforeEach(() => {
fetch.resetMocks();
});
it('calls signup and returns message to me', () => {
expect.assertions(1);
fetch.mockResponseOnce(JSON.stringify('Account Created Successfully,Please Check Your Email For Account Confirmation.' ));
//assert on the response
adminSignup.signup({
"email" : "sample#yahoo.com",
"password" : "$2a$0yuImLGh1NIoJoRe8VKmoRkLbuH8SU6o2a",
"username" : "username",
"pincode" : "1",
"city" : "Sydney",
"building" : "1",
"state" : "NSW"
}).then(res => {
expect(res).toEqual('Account Created Successfully,Please Check Your Email For Account Confirmation.');
});
//assert on the times called and arguments given to fetch
expect(fetch.mock.calls.length).toEqual(1);
});
});
I really like to create a mock file and test with that but reading jest website is not working for me.
Thanks in advance.
I have found this way(using mock-http-server) for another POST request and it works for me:
userList.js:
async function getUser (id, cb) {
const response = await fetch(`/getUserById/${id}`, {
// headers: {"Content-Type": "application/json"},
method: "POST",
body:JSON.stringify({
id : id
}),
})
.then(checkStatus)
.then(parseJSON)
.then(cb)
.catch(err => console.log(err));
const user = response.json();
return user;
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(`HTTP Error ${response.statusText}`);
error.status = response.statusText;
error.response = response;
console.log(error); // eslint-disable-line no-console
throw error;
}
function parseJSON(response) {
return response.json();
}
}
userList.test.js:
import ServerMock from "mock-http-server";
import userLists from '../components/UserList/userLists';
describe('Test with mock-http-server', function() {
// Run an HTTP server on localhost:3000
var server = new ServerMock({ host: "localhost", port: 3000 });
beforeEach(function(done) {
server.start(done);
});
afterEach(function(done) {
server.stop(done);
});
it('should do something', function(done) {
var id = 4;
server.on({
method: 'POST',
path: `/getUserById/${id}`,
reply: {
status: 200,
headers: { "content-type": "application/json" },
body: JSON.stringify({ id: 4 })
}
});
// Now the server mock will handle a GET http://localhost:3000//getUserById/${id}
// and will reply with 200 `{"id": 4}`
function cb(data) {
console.log(data);
expect(data).toBe({name:'Bob'});
done();
}
const response = userLists.getUser(4, cb);
console.log(response);
done();
});

Categories