in order to cover all statements/branch/lines, I need to write two or more fn.spec.ts to test fn.ts, how can I merge fn.spec.ts and fn2.spec.ts to be one file ?
// fn.ts
export const getEnv = () => {
if (location.href.indexOf('localhost') !== -1 || /\d+\.\d+\.\d+\.\d+/.test(location.href)) {
return 'Test'
}
return 'Product'
}
// fn.spec.ts
describe('fn getEnv',()=>{
Object.defineProperty(window, 'location', {
value: {
href: 'http://192.168.2.3:9001'
},
})
const { getEnv } = require('./fn')
test('getEnv',()=>{
expect(getEnv()).toBe('Test')
})
})
// fn2.spec.ts
describe('fn getEnv',()=>{
Object.defineProperty(window, 'location', {
value: {
href: 'https://xx.com'
},
})
const { getEnv } = require('./fn')
test('getEnv',()=>{
expect(getEnv()).toBe('Product')
})
})
// jest.config.js
module.exports = {
testEnvironment: 'jest-environment-jsdom', // browser environment
}
Just merge it into one file with a clear expectation message.
import { getEnv } from './fn';
describe('fn', () => {
describe('getEnv', () => {
test('should return "Test" when window location is an IP address', () => {
Object.defineProperty(window, 'location', {
value: {
href: 'http://192.168.2.3:9001'
},
});
const actual = getEnv();
expect(actual).toBe('Test');
});
test('should return "Product" when window location is a domain', () => {
Object.defineProperty(window, 'location', {
value: {
href: 'https://xx.com'
},
});
const actual = getEnv();
expect(actual).toBe('Product');
})
});
});
Related
I have a node.js application and I am using node mailer. When user subscribes to newsletter I send him newsletter every day at specific hour. How can I achieve that it will stop sending to that specific user after 3 days.
Code in MailServiceCron.ts:
export const CRON = () => {
scheduleJob("0 5 * * *", async () => {
try {
let UserList = await User.getUsersByDate();
UserList.forEach(async (user: IUserGet) => {
var content = fs.readFileSync("src/data/email.html");
var htmlbody = content.toString();
await fetch(
"api_url" +
process.env.USER_KEY,
{
method: "POST",
headers: { "Content-Type": "application/json" },
}
)
.then(async (res) => {
return [await res.json(), res.status];
})
.then(([data, status]) => {
console.log(data);
if (data.steviloDelovnihMest > 0) {
let transporter = nodemailer.createTransport({
host: "host",
port: 25,
secure: false, // true for 465, false for other ports
});
let info = transporter.sendMail({
from: "<no-reply#text.com>", // sender address
to: user.email, // list of receivers
subject: `test`, // Subject line// plain text body
html: htmlbody, // html body
});
}
})
.catch((error) => {
return console.log(error);
});
});
console.log("Sending mails");
} catch (e) {
console.log(e);
}
});
};
function deleteEmptyProps(obj: any): any {
Object.keys(obj).forEach((k) => {
if (
!obj[k] ||
obj[k] === undefined ||
(Array.isArray(obj[k]) && obj[k].length === 0)
) {
delete obj[k];
}
});
return obj;
}
export const deletingNonActiveCRON = () => {
scheduleJob("0 * * * *", async () => {
try {
let response = await User.deleteNonActive();
console.log(response);
} catch (e) {
console.log(e);
}
});
};
And in my separate file mail.ts i have this:
module.exports.deleteNonActive = async function () {
let date = new Date();
return await User.deleteMany({
$and: [
{ dateStart: { $lt: new Date(date.setHours(date.getHours() - 48)) } },
{ aktivnost: { $eq: false } },
],
});
};
My idea is that I need also some deleteExpired function, something like that?
module.exports.deleteExpired = async function () {
await User.updateMany(
{
$and: [
{ dateEnd: { $lt: new Date() } },
{ aktivnost: { $eq: true } },
],
},
{ $set: { aktivnost: false } }
);
};
Which I also call in MailServiceCron.ts file like deleteNonActive function?
I am using Node with websocket and I have this function:
const validatedCep = async () => {
const data = await axios
.get(`https://viacep.com.br/ws/${message}/json/`)
.then((res) => {
return res.data;
})
.catch((err) => {
return err.response;
});
console.log(1, data);
return data;
};
if (this.props.dataType === "CEP") {
validatedCep();
}
How can I get the value returned in response and access that value outside the validatedCep function?
I need this value to be able to check if it will return the value of the answer or an error, so that I can proceed with the logic of the function.
Full function:
import { MessageSender } from "./message-sender";
import { WappMessage } from "./wapp-message";
import axios from "axios";
export type FormProps = {
error?: string;
text: string;
dataType: string;
typingDuration: number;
};
export class WappFormMessage extends WappMessage<FormProps> {
constructor(
readonly props: FormProps,
private next: WappMessage<any> | undefined,
protected messageSender: MessageSender<FormProps>
) {
super(props, "response", true, messageSender);
}
getNext(message: string): WappMessage<any> | undefined {
const regexs = [
{ type: "email", regex: "^[a-z0-9]+#[a-z0-9]+\\.[a-z]+\\.?([a-z]+)?$" },
{ type: "CPF", regex: "^\\d{3}\\.?\\d{3}\\.?\\d{3}\\-?\\d{2}$" },
{ type: "CNPJ", regex: "^d{2}.?d{3}.?d{3}/?d{4}-?d{2}$" },
{
type: "cellPhone",
regex: "(^\\(?\\d{2}\\)?\\s?)(\\d{4,5}\\-?\\d{4}$)",
},
{ type: "phone", regex: "(^\\(?\\d{2}\\)?\\s?)(\\d{4}\\-?\\d{4}$)" },
{ type: "birthDate", regex: "(^\\d{2})\\/(\\d{2})\\/(\\d{4}$)" },
];
const dataTypes = [
"email",
"birthDate",
"CPF",
"CNPJ",
"cellPhone",
"phone",
];
const validateData = (element: string) => {
if (this.props.dataType === element) {
const getRegex = regexs.find((regexs) => regexs.type === element);
const regexCreate = new RegExp(getRegex!.regex, "i");
const validate = regexCreate.test(message);
return validate;
}
return true;
};
const isValid = dataTypes.find(validateData);
if (!isValid) {
return new WappFormMessage(
{
error: "Invalid data!",
...this.props,
},
this.next,
this.messageSender
);
}
const validatedCep = async () => {
const data = await axios
.get(`https://viacep.com.br/ws/${message}/json/`)
.then((res) => {
return res.data;
})
.catch((err) => {
return err.response;
});
console.log(1, data);
return data;
};
if (this.props.dataType === "CEP") {
validatedCep();
}
return this.next;
}
async send(remoteJid: string): Promise<void> {
await this.messageSender.send(
remoteJid,
this.props,
this.props.typingDuration
);
}
}
This project is to record data by AWS Timestream, and it works well.
However, I'm failed to mock AWS TimestreamWrite by using jest. I tried some ways but not working. Can someone help me?
My files as below:
ledger-service.js
const AWS = require("aws-sdk");
const enums = require("./enums");
var https = require("https");
var agent = new https.Agent({
maxSockets: 5000,
});
const tsClient = new AWS.TimestreamWrite({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: agent,
},
});
module.exports = {
log: async function (audit) {
try {
if (Object.keys(audit).length !== 0) {
if (!isPresent(audit, "name")) {
throw new Error("Name shouldn't be empty");
}
if (!isPresent(audit, "value")) {
throw new Error("Value shouldn't be empty");
}
return await writeRecords(recordParams(audit));
} else {
throw new Error("Audit object is empty");
}
} catch (e) {
throw new Error(e);
}
},
};
function isPresent(obj, key) {
return obj[key] != undefined && obj[key] != null && obj[key] != "";
}
function recordParams(audit) {
const currentTime = Date.now().toString(); // Unix time in milliseconds
const dimensions = [
// { Name: "client", Value: audit["clientId"] },
{ Name: "user", Value: audit["userId"] },
{ Name: "entity", Value: audit["entity"] },
{ Name: "action", Value: audit["action"] },
{ Name: "info", Value: audit["info"] },
];
return {
Dimensions: dimensions,
MeasureName: audit["name"],
MeasureValue: audit["value"],
MeasureValueType: "VARCHAR",
Time: currentTime.toString(),
};
}
function writeRecords(records) {
try {
const params = {
DatabaseName: enums.AUDIT_DB,
TableName: enums.AUDIT_TABLE,
Records: [records],
};
return tsClient.writeRecords(params).promise();
} catch (e) {
throw new Error(e);
}
}
ledger-service.spec.js
const AWS = require("aws-sdk");
const audit = require("./ledger-service");
describe("ledger-service", () => {
beforeEach(async () => {
jest.resetModules();
});
afterEach(async () => {
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit={
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
const mockWriteRecords = jest.fn(() =>{
console.log('mock success')
return { promise: ()=> Promise.resolve()}
});
const mockTsClient={
writeRecords: mockWriteRecords
}
jest.spyOn(AWS,'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(()=>mockTsClient);
//a=new AWS.TimestreamWrite();
//a.writeRecords(); //these two lines will pass the test and print "mock success"
await audit.log(mockAudit); //this line will show "ConfigError: Missing region in config"
expect(mockWriteRecords).toHaveBeenCalled();
});
});
I just think the the AWS I mocked doesn't pass into the ledger-service.js. Is there a way to fix that?
Thanks
updates: Taking hoangdv's suggestion
I am thinking jest.resetModules(); jest.resetAllMocks(); don't work. If I put the "It should write records when all success" as the first test, it will pass the test. However, it will fail if there is one before it.
Pass
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
Failed
it("It should throw error when audit is empty", async () => {
const mockAudit = {};
await expect(audit.log(mockAudit)).rejects.toThrow(`Audit object is empty`);
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
In ledger-service.js you call new AWS.TimestreamWrite "before" module.exports, this means it will be called with actual logic instead of mock.
The solution is just mock AWS before you call require("./ledger-service");
ledger-service.spec.js
const AWS = require("aws-sdk");
describe("ledger-service", () => {
let audit;
let mockWriteRecords;
beforeEach(() => {
mockWriteRecords = jest.fn(() => {
return { promise: () => Promise.resolve() }
});
jest.spyOn(AWS, 'TimestreamWrite');
AWS.TimestreamWrite.mockImplementation(() => ({
writeRecords: mockWriteRecords
}));
audit = require("./ledger-service"); // this line
});
afterEach(() => {
jest.resetModules(); // reset module to update change for each require call
jest.resetAllMocks();
});
it("It should write records when all success", async () => {
const mockAudit = {
name: 'testName',
value: 'testValue',
userId: 'testUserId',
entity: 'testEntity',
action: 'testAction',
info: 'testInfo',
};
await audit.log(mockAudit);
expect(AWS.TimestreamWrite).toHaveBeenCalledWith({
maxRetries: 10,
httpOptions: {
timeout: 20000,
agent: expect.any(Object),
},
});
expect(mockWriteRecords).toHaveBeenCalled();
});
});
I'm trying to test a custom hook which just returns graphql data in gatsby.
Here is what I have so far but it's giving me an error.
hook useMyData
import { useStaticQuery, graphql } from 'gatsby';
export default () => {
const {
content: { data },
} = useStaticQuery(graphql`
query myQuery {
content {
data {
views: 10
}
}
}
`);
return data;
};
Jest test
import useMyData from './useMyData';
jest.mock('./useMyData', () => ({
__esModule: true,
default: () => ({
useStaticQuery: () => ({
content: {
data: {
test: 'test',
},
},
}),
}),
}));
test('data is returned', () => {
const data = useMyData();
// console.log('data = ', data);
});
The above does not run the useStaticQuery. Any know how I would test this.
You need to directly mock the useStaticQuery method on gatsby module instead of on ./useMyData. Something along the lines of:
jest.mock('gatsby', () => ({
__esModule: true,
useStaticQuery: () => ({
content: {
data: {
test: 'test',
},
},
}),
}));
I am planning to use XState for managing states in the backend of my application. When an api is called, a function will be called on successful state change. The result of the function call has to be returned as response of the api.
// Returns a Promise, e.g.:
// {
// id: 42,
// name: 'David',
// friends: [2, 3, 5, 7, 9] // friend IDs
// }
function getUserInfo(context) {
return fetch('/api/users/#{context.userId}').then(response =>
response.json()
);
}
// Returns a Promise
function getUserFriends(context) {
const { friends } = context.user;
return Promise.all(
friends.map(friendId =>
fetch('/api/users/#{context.userId}/').then(response => response.json())
)
);
}
const friendsMachine = Machine({
id: 'friends',
context: { userId: 42, user: undefined, friends: undefined },
initial: 'gettingUser',
states: {
gettingUser: {
invoke: {
src: getUserInfo,
onDone: {
target: 'gettingFriends',
actions: assign({
user: (context, event) => event.data
})
}
}
},
gettingFriends: {
invoke: {
src: getUserFriends,
onDone: {
target: 'success',
actions: assign({
friends: (context, event) => event.data
})
}
}
},
success: {
type: 'final'
}
}
});
interpret(friendsMachine).start()
I want the output of this of getUserFriends sent as a response from my api. How to wait for the transition and all the invocations to be completed?
You can use onDone (read the docs on invoking promises 📖)
Here's an example Express app that waits sequentially for 2 promises to finish, and then sends that data:
function eventuallyGet(value) {
return new Promise(res => {
setTimeout(() => {
res(value);
}, 1000)
})
}
const getUserMachine = Machine({
initial: 'fetchingName',
context: {
user: undefined
},
states: {
fetchingName: {
invoke: {
src: () => eventuallyGet('David'),
onDone: {
target: 'fetchingDetails',
actions: assign({
user: (ctx, e) => ({
...ctx.user,
name: e.data
})
})
}
}
},
fetchingDetails: {
invoke: {
src: () => eventuallyGet({ location: 'Florida' }),
onDone: {
target: 'success',
actions: assign({
user: (ctx, e) => ({
...ctx.user,
...e.data
})
})
}
}
},
success: {
type: 'final',
data: {
user: ctx => ctx.user
}
}
}
});
app.get('/user', function(request, response) {
interpret(getUserMachine)
.onDone(e => {
response.json(e.data);
})
.start();
});
You can see the code here: https://glitch.com/~pleasant-relish