How to write jest test script to check the value of constants - javascript

I have a list of constants defined like this
const actions = {}
// Home
actions.HOME = {}
actions.HOME.SET_PROFILE_ID = 'SET_PROFILE_ID'
actions.HOME.LOAD_PROFILE = 'HOME_LOAD_PROFILE'
actions.HOME.SET_PROFILE = 'HOME_SET_PROFILE'
actions.OUTSIDE = {}
actions.OUTSIDE.UPDATE_PROFILE_ID = 'SET_PROFILE_ID' // this should error
module.exports = actions
The objects with in objects is to help intelisense so devs can narrow down as they go.
I want to use jest to write a test that will check to make sure no 2 constants have the same value, no matter the depth, otherwise it can create very odd errors that are hard to debug at run time. I don't really understand the documentation and how I can do this. https://jestjs.io/docs/en/using-matchers But this is my first time making any unit tests.
Thank you
-Edit
This is what I have so far. Based on Jared Smiths comments, I am no where close to the right answer as this is too simple. It only finds the first mistake, not all of them.
describe('Actions.js', () => {
it('verify no duplicate action values', () => {
const flattenActions = []
_.forEach(actions, store => {
_.forEach(store, action => {
flattenActions.push(action)
})
})
const testedActions = []
_.forEach(flattenActions, action => {
expect(testedActions).not.toContain(action)
testedActions.push(action)
})
})
})

First of all you can get all the values of your actions
function getDeepValues(obj) {
let values = [];
for (const key in obj) {
if (typeof obj[key] === 'object') {
const subVals = getDeepValues(obj[key]);
values = [...values, ...subVals];
} else {
values.push(obj[key]);
}
}
return values;
}
Will output something like this:
[ 'SET_PROFILE_ID', 
'HOME_LOAD_PROFILE', 
'HOME_SET_PROFILE', 
'SET_PROFILE_ID' ]
And then you test if the array doesn't contain any duplicates:
function arrayHasNoDuplicate(arr) {
return arr.every(num => arr.indexOf(num) === arr.lastIndexOf(num));
};
Now you have to run your tests:
describe('Actions.js', () => {
it('verify no duplicate action values', () => {
const actionsArray = getDeepValues(actions);
const hasNoDuplicates = arrayHasNoDuplicate(actionsArray);
expect(hasNoDuplicates).toBeTruthy();
})
})
Hope it helps!

A unit test of this complexity probably merits its own matcher, which you could define recursively like this:
expect.extend({
toHaveUniqueValues(received) {
const keys = []
const values = new Set()
function assertUniqueValues(object) {
if (typeof object === 'object' && object !== null) {
for (const key in object) {
if (object.hasOwnProperty(key)) {
keys.push(key)
assertUniqueValues(object[key])
keys.pop()
}
}
} else if (values.has(object)) {
throw new Error(`expected ${keys.join('.')} to not have duplicate value ${String(object)}`)
}
values.add(object)
}
try {
assertUniqueValues(received)
return {
message: () => 'expected object to have duplicate values',
pass: true
}
} catch (error) {
return {
message: () => error.message,
pass: false
}
}
}
})
The message that goes with pass: true, as explained in the documentation, is in case the test is negated and the negation of the test fails. Set is preferred to Array for storing the values found so far, because lookup using has() is O(1) time on average while using includes() is O(n) time.
To use the above matcher in your case:
describe('actions', () => {
it('should not have duplicate values', () => {
expect(actions).toHaveUniqueValues()
})
})
In this case it will complete with the error:
expected OUTSIDE.UPDATE_PROFILE_ID to not have duplicate value SET_PROFILE_ID

Related

How to build an object tree with recursive API calls?

I want to construct a tree where each node is used in a API call to get the children nodes; starting at the root. And this will be done recursively until it reaches the TREE_DEPTH_LIMIT
export const search = async (searchTerm) => {
try {
const tree = {};
await createTree(searchTerm, tree);
return tree;
} catch (err: any) {}
};
const TREE_DEPTH_LIMIT = 3;
const createTree = async (searchTerm, tree) => {
if (counter === TREE_DEPTH_LIMIT) {
counter = 0;
return;
}
counter++;
tree[searchTerm] = {};
try {
const res = await axiosInstance.get(
`/query?term=${searchTerm}`
);
// res.data.terms is an array of strings
res.data.terms.forEach((term) => {
createTree(term, tree[searchTerm]);
});
} catch (err) {}
};
I am trying to do this recursively in the createTree() function above. It will use the searchTerm in the API call. Then it will loop through res.data.terms and call createTree() on each on the terms. But the output is not what I was expecting.
This is the output:
const tree = {
apple: {
apple_tree: {},
tree: {},
},
};
The expected output: (because the TREE_DEPTH_LIMIT is 3, it should have 3 levels in the tree)
const tree = {
apple: {
apple_tree: {
leaf: {},
},
tree: {
trunk: {},
},
},
};
Or is my solution completely incorrect and I should be going for another approach??
Some issues:
counter seems to be a global variable, but that will not work out well as at each return from recursion, counter should have its value restored. It is better to use a local variable for that, so that every execution context has its own version for it. Even better is to make it a parameter and let it count down instead of up.
The recursive call is not awaited, so in search the promise returned by createTree may resolve before all the children have been populated, and so you would work with an incomplete tree.
Not a real problem, but it is not the most elegant that the caller must create a tree object and pass it as argument. I would redesign the functions so that search will create that object itself, and then use a recursive function to create children -- so I'd name that function createChildren, instead of createTree.
Here is a snippet that first mocks the get method so you can actually run it:
// Mock for this demo
const axiosInstance = {async get(term) {const delay = ms => new Promise(resolve => setTimeout(resolve, ms));await delay(50);return {data: {terms: {"apple": ["apple_tree", "tree"],"apple_tree": ["leaf"],"leaf": [],"tree": ["trunk"],"trunk": []}[term.split("=")[1]]}};}}
const createChildren = async (searchTerm, depth) => {
if (depth-- <= 0) return {};
try {
const res = await axiosInstance.get(`/query?term=${searchTerm}`);
const promises = res.data.terms.map(async (term) =>
[term, await createChildren(term, depth)]
);
return Object.fromEntries(await Promise.all(promises));
} catch (err) {
console.log(err);
}
};
const TREE_DEPTH_LIMIT = 3;
const search = async (searchTerm, depth=TREE_DEPTH_LIMIT) =>
({[searchTerm]: await createChildren(searchTerm, depth)});
// Demo
search("apple").then(tree =>
console.log(tree)
);

Cypress: How to get value of div (mixing up async and sync code)

I have a div with the following html
<div data-cy="pop" style="margin-right:5px;">12,300</div>
I am trying to get 12,3001 convert it to a int and save the value for use in another function. I am getting this error.
cy.then() failed because you are mixing up async and sync code.The value you synchronously returned was: 12300
Here is my code to get the value.
cy.elem('pop').invoke('text').then((num) => {
const res = parseInt(num.replaceAll(',',''))
return res
})
Does anyone know what I'm doing wrong?
It's not clear how you are attempting to pass the value of your parsed int into the another function.
One way to save the value of something is with an alias. To do that you will need to use the .as().
cy.elem('pop')
.invoke('text')
.then((num) => { return parseInt(num.replaceAll(',','')) })
.as('num')
// later down in your code
cy.get('#num')
.then(number => {
functionYouWantToPassYourNumber(number)
})
Bit of an edge-case, TLDR: return cy.wrap(res).
If I run this as a minimal test for your code, it passes
const num = "12,300";
cy.wrap(num).then((numAsString) => {
const numAsInt = parseInt(numAsString.replace(",", ""));
return numAsInt
})
.then(num => {
cy.log(num) // logs 12300 ✅
})
If I add an async line cy.wait(5000) (for example), it fails
const num = "12,300";
cy.wrap(num).then((numAsString) => {
const numAsInt = parseInt(numAsString.replace(",", ""));
cy.wait(5000)
return numAsInt
})
.then(num => {
cy.log(num) ❌
})
If I then cy.wrap() the result, it passes again
const num = "12,300";
cy.wrap(num).then((numAsString) => {
const numAsInt = parseInt(numAsString.replace(",", ""));
cy.wait(5000)
return cy.wrap(numAsInt)
})
.then(num => {
cy.log(num) // logs 12300 ✅
})
Theoretically your code should pass, but if you have another command inside the .then() that could be causing it.
Or possible the cy.elem('pop') is causing it.
For reference, this is Cypress' own test for the error
describe("errors", {defaultCommandTimeout: 100}, () => {
beforeEach(function () {
this.logs = [];
cy.on("log:added", (attrs, log) => {
this.lastLog = log;
this.logs?.push(log);
});
return null;
});
it("throws when mixing up async + sync return values", function (done) {
cy.on("fail", (err) => {
const { lastLog } = this;
assertLogLength(this.logs, 1)
expect(lastLog.get("error")).to.eq(err);
expect(err.message).to.include(
"`cy.then()` failed because you are mixing up async and sync code."
);
done();
});
cy.then(() => {
cy.wait(5000);
return "foo";
});
});
});

Instanceof 'Class' return false using a mock object

I'm testing a method and I need to use a mock object as parameter (I am using typemoq). The problem is that the instanceof check, always return false. How can I solve?
public foo(container: BaseClass): string {
if (container instanceof Application) { // Always returns false when I use a mock of the object
// do something
} else {
// do something else
}
return retval;
}
In my spec.ts
context('foo', () => {
it('should return...', () => {
const container = Mock.ofType<Application>();
const result = class.foo(container.object);
});
});
I tried this too, but the result is the same.
context('foo', () => {
it('should return...', () => {
const application = new Application();
const container = Mock.ofInstance(application);
const result = class.foo(container.object);
});
});
How can I fix this? I need to test what append when the parameter is an instance of 'Application'.
Thank you.
I have found a solution. Just change .object in .target in the tests.
context('foo', () => {
it('should return...', () => {
const application = new Application();
const container = Mock.ofInstance(application);
const result = class.foo(container.target); // instead of .object
});
});
In this way it is possible to mock the methods of 'Application' class and the 'instanceof Class' will return true

ReactJS how to wait for all API calls to be ended in componentDidMount of simple component

I'm using latest react and very basic app which calls 3rd party service API which actually is not well designed in meaning of following.
I have to execute one call which return list and then have to iterate and call other end point to get data for item from list and then again in data have new list for which I have to call 3rd API end point.
After I receive all data I combined it to one items array and place it in state in componentDidMount function but this final step works only if I surround it with setTimeout.
Is there some elegant way to do that?
I'm using fetch and really pure react components, have my own simple service from where I call API, here is some code parts...
items[tag].sensors = [];
API.getObjects(sessionData, userDetails, tag).then(links => {
Object.keys(links.link).forEach(link => {
API.getObjects(sessionData, userDetails, link).then(objLink => {
Object.keys(objLink.link).forEach(function (key) {
let obj = objLink.link[key];
if (obj && obj.type === 'sensor') {
API.getSensorNames(sessionData, key).then(response => {
const sensor = response.sensor;
// some sensor calculations....
items[tag].sensors.push(sensor);
});
}
});
});
});
});
// this part only works if it's surrounded with timeout
setTimeout(function() {
let processedItems = [];
for (var key in items) {
if (items.hasOwnProperty(key)) {
processedItems.push(items[key]);
}
}
self.setState({
items: processedItems
});
}, 1000);
Thanks in advance.
Simply, You can use Promise to wait until you get values from the API call, therefore you will put your code in function like this
function prepareItems() {
items[tag].sensors = [];
return new Promise((resolve, reject) => {
API.getObjects(sessionData, userDetails, tag).then(links => {
Object.keys(links.link).forEach(link => {
API.getObjects(sessionData, userDetails, link).then(objLink => {
Object.keys(objLink.link).forEach(function(key) {
let obj = objLink.link[key];
if (obj && obj.type === "sensor") {
API.getSensorNames(sessionData, key).then(response => {
const sensor = response.sensor;
// some sensor calculations....
items[tag].sensors.push(sensor);
// whenever you set resolve it will end the promise
//and pass the result it to the then function
resolve(items)
});
}
});
});
});
});
});
}
and use then to get the result from the prepareItems function after its resolved
prepareItems().then(items => {
//Do what ever you want with prepared item
})
What about using async/await operators.
These operators allows you to wait until the response is ready.
You can use this kind of helper function.
getItems = async (...) => {
...
items[tag].sensors = []
const links = await API.getObjects(sessionData, userDetails, tag)
Object.keys(links.link).forEach(async (link) => {
const objLink = await API.getObjects(sessionData, userDetails, link)
Object.keys(objLink.link).forEach(async (key) => {
let obj = objLink.link[key]
if (obj && obj.type === 'sensor') {
const response = await API.getSensorNames(sessionData, key)
const sensor = response.sensor
items[tag].sensors.push(sensor)
}
})
})
this.setState({ items })
}
Also you can see this great documentation.

JavaScript - ES6 function does not work for some weird reason

I am setting the below function in order to retrieve it after.
However, for some reason, it does not work:
constructor(private nativeStorage: NativeStorage) {
// I am calling it this way:
this.userId = 123;
this.getItem("userId", this.userId);
// If I replace the shortcut by the below it works fine
/* this.nativeStorage.getItem("userId).then(
data => this.userId = data,
error => console.error(error)
); */
}
getItem(itemKey, itemValue) {
return this.nativeStorage.getItem(itemKey).then(
data => itemValue = data,
error => console.error(error)
);
}
I believe that I am missing something here, that's why it doesn't work
You're assigning data to itemValue which is just a copy of this.userId. JS doesn't support pass by reference that could make that possible. Instead you can just use itemKey to assign to the class instance directly like this:
getItem(itemKey) {
return this.nativeStorage.getItem(itemKey).then(
data => this[itemKey] = data, // assign data to this[itemKey] which will be this.userId (in the example above)
error => console.error(error)
);
}
JavaScript is not supporting call by reference. It's only by value arg passing. So values can be updated only by object references only.
In reference to the part/comments where you can't have an async constructor and therefore I extract all the async parts into a factory.
I have no experience yet with ionic, so you should take the following as pseudocode:
import { NativeStorage } from '#ionic-native/native-storage';
function constructor(public userId: String){ }
//and the factory function
//don't know how exactly how to export stuff in that framework
function createById(userId){
return NativeStorage.getItem('userId')
.then(
userId => new YourClass(userId)
error => console.error(error)
)
}
or in the case where you want to assign multiple properties:
//a utility to resolve with { key: Promise<value> } mappings + pretty much everything you throw at it.
//warning: doesn't resolve promises in nested structures (by design),
//stuff like {key: { foo: Promise<value> }}
//ideally you'd put that in a seperate module since it can be handy in many places.
function resolve(obj){
if(Array.isArray(obj)
return Promise.all(obj);
if(typeof obj === "function")
return Promise.resolve().then(obj);
if(!obj || typeof obj !== "object" || "then" in obj && typeof obj.then === "function")
return Promise.resolve(obj);
var keys = Object.keys(obj);
return Promise.all( keys.map(k => obj[k]) )
.then(values => combine(keys, values));
}
//takes two Arrays, a matching set of keys and values and combines that into an object.
function combine(keys, values){
return keys.reduce((acc, key, index) => {
acc[key] = values[index];
return acc;
}, {});
}
const assignTo = target => source => Object.assign(target, source);
//in this case we use assign all async values outside of the constructor
function constructor(){
this.userId = null;
this.userName = null;
}
function createById(userId){
return resolve({
userId: NativeStorage.getItem('userId'),
userName: NativeStorage.getItem('userName'),
//...
}).then(
assignTo( new YourClass() ),
error => console.error(error)
)
}
Or if this is still to much repetition for you:
//a utility to fetch multiple items at once
function getItems(...keys){
return Promise.all( keys.map( key => NativeStorage.getItem(key) ) )
.then(values => combine(keys, values));
}
//and a quick test
getItems("userId", "userName", ...).then(console.log);
//or in the context of the last snippet:
getItems("userId", "userName", ...).then(assignTo( new YourClass() ));
Hope this helps shows you a different approach to your problems.

Categories