I'm testing a function with partially randomized input. The pieces of the input object that are randomized should work no matter what but if I happen to get a case that fails I'd like to know what it is by including the invalid input in my failure message.
How can I get Jest to show a failure message that extends further than just the contents of expect?
Here is the code I have:
describe("my module", () => {
it("should pass with some randomized inputs", () => {
const partialRandomInput = {
name: "Hardcoded",
age: Math.random() * 100,
yearsOfExperience: Math.random() * 30
};
const actualOutput = myModule(partialRandomInput);
expect(actualOutput).toEqual(false); // If this fails I only see the expect comparison that happened, which may not be helpful
});
});
Perhaps in my code above, the only failure would be if the randomized age is less than 5 and the randomized yearsOfExperience is less than 10. I'd like to see the values used for partialRandomInput when my test fails.
Do not test with random input.
Even if the code is meant to be used with random number, you should test it with predefined values to ensure it's consistent results. By using random numbers you cannot ensure that the used input will cover the same "branches" within the implementation.
What you can try instead is have a predefined set of values and expected results
describe.each([
[10, 10, true],
[22, 10, false],
[35, 11, true],
[50, 3, false],
[60, 7, false],
])(
"with age: %p and yearsOfExperience: %p",
(age, yearsOfExperience, expected) => {
it(`should return: ${expected}`, () => {
expect(
myModule({
name: "Hardcoded",
age,
yearsOfExperience,
})
).toEqual(expected);
});
}
);
But in order to answer the question - you can generate the random numbers before the execution of the test and you can then either:
Extend expect
Add the value within the description of the test
describe.each(
Array.from(new Array(10), () => [Math.random() * 100, Math.random() * 10])
)("with age: %p and yearsOfExperience: %p", (age, yearsOfExperience) => {
test(`returns false`, () => {
expect(
myModule({
name: "Hardcoded",
age,
yearsOfExperience,
})
).toBe(false);
});
});
Related
I have list of functions to be executed based on the value of a variable, age. These functions correspond to a number of vaccine doses given to an individual.
If childern, then no function (i.e. vaccine) is to be executed.
If youth then function firstDose is to be executed.
If adult then two functions, firstDose and secondDose is to be
executed.
If old then three functions, firstDose, secondDose, boosterDose to be executed
Currently, I build this list up manually, with switch and case statements. If the condition is satisfied, then I push the required functions to an array of functions to execute later, like so for old:
vaccines.push(() => this.firstDose(ageVerification))
vaccines.push(() => this.secondDose(ageVerification, previousVaccineCertificate))
vaccines.push(() => this.boosterDose(ageVerification, previousVaccineCertificate, medicalDocuments))
Instead of embedding this in code, I would like to base it on configuration (currently in a file, may move to a database at a later point in time). The constants in a file should look like this:
export const ageVaccineMapping = {
children: null,
youth: [this.firstDose(ageVerification)],
adult:[this.firstDose(ageVerification), this.secondDose(ageVerification, previousVaccineCertificate)],
old:[this.firstDose(ageVerification), this.secondDose(ageVerification, previousVaccineCertificate), this.boosterDose(ageVerification, previousVaccineCertificate, medicalDocuments)],
}
My Question is, How should I create such a file of constants with varying function argument, how do I import constant file in working ts file, and how do I access that function array in code?
I am using Typescript and promise.all to execute. Any leads will be helpful. Anonymous functions are also acceptable.
Please not that, this is just example, this is similar to my use-case. For old people, I want to execute all 3 functions and not just boosterDose function.
Also, I want the constant list should have varying list of function arguments.
Assuming you have this mapping definition in JSON:
{
"groups": {
"children": [],
"youth": ["firstDose"],
"adult": ["firstDose", "secondDose"],
"old": ["firstDose", "secondDose", "boosterDose"]
},
"arguments": {
"firstDose": ["ageVerification"],
"secondDose": ["ageVerification", "previousVaccineCertificate"],
"boosterDose": ["ageVerification", "previousVaccineCertificate", "medicalDocuments"]
}
}
and your person objects look like this:
const people = [
{group: 'adult', ageVerification: 'A1', previousVaccineCertificate: 'B1', medicalDocuments: null},
{group: 'children', ageVerification: null, previousVaccineCertificate: null, medicalDocuments: null},
{group: 'old', ageVerification: 'A3', previousVaccineCertificate: 'B3', medicalDocuments: 'C3'},
{group: 'youth', ageVerification: 'A4', previousVaccineCertificate: null, medicalDocuments: null},
];
Then you could create verification service that maps the property names from the JSON into functions and arguments:
class VerificationService {
mapping;
constructor(mapping) {
this.mapping = mapping;
}
verifyAsync(person) {
const pendingVerifications = this.mapping.groups[person.group].map(funcName => {
const args = this.mapping.arguments[funcName].map(prop => person[prop]);
return this[funcName](...args);
});
return Promise.all(pendingVerifications).then(results => results.every(r => r));
},
firstDose(age) {
console.log('--> verifying firstDose:', [...arguments].join(', '));
return Promise.resolve(true); // simulate async result
},
secondDose(age, previous) {
console.log('--> verifying secondDose:', [...arguments].join(', '));
return Promise.resolve(true); // simulate async result
},
boosterDose(age, previous, medical) {
console.log('--> verifying boosterDose:', [...arguments].join(', '));
return Promise.resolve(true); // simulate async result
}
}
Called like this:
const mapping = JSON.parse('(the above mapping definition)');
const verificationService = new VerificationService(mapping);
people.forEach(async (person) => {
try {
console.log('verifying person', JSON.stringify(person));
const verified = await verificationService.verifyAsync(person);
console.log('result', verified);
} catch (err) {
console.log('verification error', err);
}
});
node.js produces this output:
verifying person {"group":"adult","ageVerification":"A1","previousVaccineCertificate":"B1","medicalDocuments":null}
--> verifying firstDose: A1
--> verifying secondDose: A1, B1
verifying person {"group":"children","ageVerification":null,"previousVaccineCertificate":null,"medicalDocuments":null}
verifying person {"group":"old","ageVerification":"A3","previousVaccineCertificate":"B3","medicalDocuments":"C3"}
--> verifying firstDose: A3
--> verifying secondDose: A3, B3
--> verifying boosterDose: A3, B3, C3
verifying person {"group":"youth","ageVerification":"A4","previousVaccineCertificate":null,"medicalDocuments":null}
--> verifying firstDose: A4
result true
result true
result true
result true
The dummy return Promise.resolve(true); in the worker functions is async, but it has no delay. With actual asynchronous results, the output will be randomly ordered, but the above serves as a demo only anyway.
I started to work in a new project where I found lodash's flow function and I saw the uses of it here docs but in my project, in the following code, I found over there flow([...])(state) here what is (state) at the end of the function?
module.exports = (async function published(state) {
return flow([
setColumnIndex('my_pay_table', 1, 'rate_mode', getColumn('pay_structure', 'pay_per_id', state)),
setColumnIndex('my_pay_table', 1, 'rate_amount', getColumn('pay_structure', 'pay_rate', state)),
setColumnIndex('my_wo_title_table', 1, 'user_id', buildArtifact(ownerAlias, 'user', 'id', 1)),
setColumnIndex('my_wo_title_table', 1, 'date_added', Date.now() / 1000),
])(state);
});
Can anyone help me?
According to the lodash documentation, flow returns a function. In JavaScript it is possible to return functions without executing them.
We could refactor the code you provided to the following
module.exports = (async function published(state) {
// `func` here is a function
const func = flow([
setColumnIndex('my_pay_table', 1, 'rate_mode', getColumn('pay_structure', 'pay_per_id', state)),
setColumnIndex('my_pay_table', 1, 'rate_amount', getColumn('pay_structure', 'pay_rate', state)),
setColumnIndex('my_wo_title_table', 1, 'user_id', buildArtifact(ownerAlias, 'user', 'id', 1)),
setColumnIndex('my_wo_title_table', 1, 'date_added', Date.now() / 1000),
]);
// Here we execute that function with an argument `state`
return func(state);
});
So far I found the solution. It actually uses Lodash's curry function.
let state = "Initial State";
const setColumnIndex = _.curry((table, index, column, value, state) => {
if (typeof index !== 'number') {
throw new Error(`Tried to setColumnIndex and specified a non-numeric index parameter (1): ${index}, did you mean to call setColumn?`);
}
return "New state"; // state = "Setting up new state here";
});
let result =_.flow([
setColumnIndex('my_wo_table', 1, 'status_id', 2),
setColumnIndex('my_wo_table', 1, 'label_id', 1),
setColumnIndex('my_wo_table', 1, 'date_added', Date.now() / 1000),
])(state);
console.log(result); //=> "New state"
In the above code, if we notice that for setColumnIndex function has 5 parameters when we are calling from flow function, actually passing 4 parameters and with (state) in curry style total 5 parameters.
I'm trying write a jest test case that tests an async method, I want to pass in the done() parameter so jest waits for it to be fired before it ends the test, however, I'm not sure where to put it.
Any ideas?
const testcases = [
[
'Crew',
[1,2,3],
Enum.Level1
],
[
'Staff',
[4,5,6],
Enum.Level2
]
];
test.each(testcases )(
'Should be able to load differing cases %p',
(
typeName: string,
initalVals: string[],
type: LevelType
) => {
// some call that updates mobx store state
when(
() => mobxstoreProperty.length == initalVals.length,
() => {
// my assertions
done();
}
);
}
);
For a single jest test I can do this:
test('my single test', done => {
// some call that updates mobx store state
when(
() => mobxstoreProperty.length == initalVals.length,
() => {
// my assertions
done();
}
);
});
Just unsure how to do it for when I use the test.each method.
I use named parameters and I can add the done() method as the last function parameter. For example like so:
const testcases: {
typeName: string;
initalVals: string[],
type: LevelType
}[] = [
{
typeName: 'Crew',
initalVals: [1,2,3],
type: Enum.Level1
},
{
typeName: 'Staff',
initalVals: [4,5,6],
type: Enum.Level2
},
];
test.each(testcases)(
'Should be able to load differing cases %p',
// Must use `any` for `done`, as TypeScript infers the wrong type:
({typeName, initalVals, type}, done: any) => {
// some call that updates mobx store state
when(
() => mobxstoreProperty.length == initalVals.length,
() => {
// my assertions
done();
}
);
}
);
I haven't tested if you can just add the done() method as last parameters with array arguments, but maybe that works, too.
to pass and evaluate done, the done callback should be very last argument in the function for test case arguments.
also, here's how to deal with typings, when you use the test.each method in typescript:
// found at https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34617
it.each<number | jest.DoneCallback>([1, 2, 3])(
'dummy: %d',
(num: number, done: jest.DoneCallback) => {
done();
},
);
There is no perfect answer at the moment, as there is an issue in the Jest library with the templated types of test.each()
So for now, all solutions to achieve what you want, require to do some tricks with the types.
A solution for complex test parameters, with the light array syntax definition:
test.each<Array<string | string[] | LevelType | jest.DoneCallback>>([
["Crew", [1, 2, 3], LevelType.Level1],
["Staff", [4, 5, 6], LevelType.Level2],
])(
"Should be able to load differing cases %p",
(
typeName: string,
initalVals: string[],
type: LevelType,
done: jest.DoneCallback
) => {
// some test code
}
);
The trick in this solution is the use of test.each<Array<string | string[] | LevelType | jest.DoneCallback>> that bypasses the templated type issue.
For more information, see the opened issues https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34617 and https://github.com/facebook/jest/issues/8518
Edit:
This solution has been edited to replace the initial any type in by a more precise one.
Thanks to Alejandro Moreno for the idea in the comments
The goal is to group items in a stream into multiple groups. Run transformations on those groups separately and then re-combine all the groups into one stream.
The only call I can use after group seems to be each and that doesn't pass individual groups to my callback it passes the entire dictionary of grouped objects. Calling map won't pass anything to my callback. Example:
const groups = stream.group(func);
groups.map(item => console.log(item)); // prints nothing
groups.each(item => console.log(item)); // instead of item being one of the groups created, it's a dictionary with all the groups included.
How can I do that?
are you looking for doing this ? :
groups.on('error', handleError)
.pipe(transformObject(async item => {
some code...
If non, please try to give an example of what you want to do with your data.
import highland from 'highland';
const source = highland([
{ age: 21, name: 'Alice' },
{ age: 42, name: 'Bob' },
{ age: 42, name: 'Carol' }
]);
source
.group(person => person.age)
.flatMap(highland.values)
// Apply transformation to each group here
.map(group => group)
.flatten(1)
.doto(console.log)
.done(() => {});
I've been working on a project with nodejs and pg-promise. Couldn't quite figure out how to implement parameterized query. Here how my code looks like:
// let's say we have an array
var hex = new Array;
hex = [[0, 1, 2, 'string1', 3, 'string2'],[4, 5, 6, 'string3', 7, 'string4']];
//lets assume db is initialized with correct parameters and we have a for loop
for (var i=0; i<hex.length; i++){
db.tx(t => {
return t.batch([
t.none('INSERT INTO x (lat, long, name) VALUES ($1[$2][0], $1[$2][1],
$1[$2][4];)',[hex,i]),
t.none('DELETE FROM x WHERE name='y'; ')];
)}
.then(data => {})
.catch(error => {
console.log('ERROR:', error);
});
)
}
My guess is that I'm not coding in right syntax especially in
t.none('INSERT INTO x (lat, long, name) VALUES ($1[$2][0], $1[$2][1], $1[$2][4];)',[hex,i]
I've read about the difference between passing arrays and parameters-functions in Vitaly's documentation. But couldn't merge them together since I'm guessing I need them both.
What am I doing wrong here?
EDIT: btw I'm getting syntax error near '['.
Thanks in advance!