Using test.each in Jest, I'd like to pass in a Boolean that toggles whether I expect something or not.
This is the type of repetitive code I'm trying to avoid:
if (expectFoo) {
expect(anObject).toContainEqual(
expect.objectContaining({
name: "foo"
})
);
} else {
expect(anObject).not.toContainEqual(
expect.objectContaining({
name: "foo"
})
);
}
Here is some pseudocode that illustrates my preferred (cleaner, shorter) approach:
expect(anObject).yesOrNo(expectFoo).toContainEqual(
expect.objectContaining({
name: "foo"
})
);
Is there anything like this in Jest?
Built-in matchers aren't public API but can be imported from the same internal module that is used by Jest:
import matchers from 'expect/build/matchers';
Custom matchers can wrap and compose built-in matchers:
expect.extend({
toContainFoo(received, expectFoo = true) {
const isNot = !expectFoo ? !this.isNot : this.isNot
this.isNot = isNot; // affects error message
const expected = matchers.objectContaining({
name: "foo"
});
const result = matchers.toContainEqual.call(this, received, expected);
result.pass = !isNot; // affects assertion logic
return result;
}
})
Related
I'm trying to write an eslint rule that enforces making sure the name property is defined on any classes that extend from other Error/Exception named classes (and fixes them).
As far as I can tell, it works in the astexplorer.net individually, but when I'm running it alongside other rules, it ends up getting ran multiple times, so the name property ends up being repeated multiple times in the resulting "fixed" file.
Is there anything in particular I can do to prevent it being run multiple times? I'm assuming what's happening is that it's inserting my name = 'ClassName';, then prettier is needing to reformat the code, which it does, but then maybe it's re-running my rule? I'm not sure.
Rule/fix code shown below. I've tried things like using *fix and yield, but that doesn't seem to help either (see commented code below, based on information in the eslint documentation)
module.exports = {
meta: {
hasSuggestions: true,
type: 'suggestion',
docs: {},
fixable: 'code',
schema: [], // no options,
},
create: function (context) {
return {
ClassDeclaration: function (node) {
const regex = /.*(Error|Exception)$/;
// If the parent/superClass is has "Error" or "Exception" in the name
if (node.superClass && regex.test(node.superClass.name)) {
let name = null;
const className = node.id.name;
// Test class object name
if (!regex.test(className)) {
context.report({
node: node,
message: 'Error extensions must end with "Error" or "Exception".',
});
}
// Find name ClassProperty
node.body.body.some(function (a) {
if (a.type === 'ClassProperty' && a.key.name === 'name') {
name = a.value.value;
return true;
}
});
// Name property is required
if (!name) {
context.report({
node: node,
message: 'Error extensions should have a descriptive name',
fix(fixer) {
return fixer.replaceTextRange(
[node.body.range[0]+1, node.body.range[0]+1],
`name = '${className}';`
);
},
// *fix(fixer) {
// name = className;
// yield fixer.replaceTextRange(
// [node.body.range[0]+1, node.body.range[0]+1],
// `name = '${className}';`
// );
//
// // extend range of the fix to the range of `node.parent`
// yield fixer.insertTextBefore(node.body, '');
// yield fixer.insertTextAfter(node.body, '');
// },
});
}
}
},
};
},
};
Turns out I had the AST Explorer set to the wrong parser, so it was showing me the wrong string name for the ClassProperty node. I should have been using PropertyDefinition instead.
I have a type:
type button = JSX.Element | null;
and a function:
const getFirstButton = (buttonArray: button[], first: boolean) => {
if (first) {
return buttonArray[1];
}
return buttonArray.find(b => b !== null);
};
here is my test
test('getFirstButton', () => {
const buttons = // what goes here?
expect(getFirstButton(buttons, false)).toContain('button_1');
});
I need help with the second line on the test. How do i handle this?
Is this even possible?
Note: my test file is test.ts and I don't want to change it .tsx
JSX.Element is an object created by JSX syntax, i.e. React.createElement.
It can be:
const buttons = [null, null, <p/>, <div/>];
expect(getFirstButton(buttons, false)).toBe(buttons[2]);
Notice that JavaScript arrays are zero-based, so buttonArray[1] is possibly a mistake that will be detected when covering if (first) condition.
The test is linked to this question here which I raised (& was resolved) a few days ago. My current test is:
// Helpers
function getObjectStructure(runners) {
const backStake = runners.back.stake || expect.any(Number).toBeGreaterThan(0)
const layStake = runners.lay.stake || expect.any(Number).toBeGreaterThan(0)
return {
netProfits: {
back: expect.any(Number).toBeGreaterThan(0),
lay: expect.any(Number).toBeGreaterThan(0)
},
grossProfits: {
back: (runners.back.price - 1) * backStake,
lay: layStake
},
stakes: {
back: backStake,
lay: layStake
}
}
}
// Mock
const funcB = jest.fn(pairs => {
return pairs[0]
})
// Test
test('Should call `funcB` with correct object structure', () => {
const params = JSON.parse(fs.readFileSync(paramsPath, 'utf8'))
const { arb } = params
const result = funcA(75)
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure(arb.runners)
)
)
)
})
The object structure of arb.runners is this:
{
"back": {
"stake": 123,
"price": 1.23
},
"lay": {
"stake": 456,
"price": 4.56
}
}
There are many different tests around this function mainly dependent upon the argument that is passed into funcA. For this example, it's 75. There's a different length of array that is passed to funcB dependent upon this parameter. However, it's now also dependent on whether the runners (back and/or lay) have existing stake properties for them. I have a beforeAll in each test which manipulates the arb in the file where I hold the params. Hence, that's why the input for the runners is different every time. An outline of what I'm trying to achieve is:
Measure the array passed into funcB is of correct length
Measure the objects within the array are of the correct structure:
2.1 If there are stakes with the runners, that's fine & the test is straight forward
2.2 If not stakes are with the runners, I need to test that; netProfits, grossProfits, & stakes properties all have positive Numbers
2.2 is the one I'm struggling with. If I try with my attempt below, the test fails with the following error:
TypeError: expect.any(...).toBeGreaterThan is not a function
As with previous question, the problem is that expect.any(Number).toBeGreaterThan(0) is incorrect because expect.any(...) is not an assertion and doesn't have matcher methods. The result of expect.any(...) is just a special value that is recognized by Jest equality matchers. It cannot be used in an expression like (runners.back.price - 1) * backStake.
If the intention is to extend equality matcher with custom behaviour, this is the case for custom matcher. Since spy matchers use built-in equality matcher anyway, spy arguments need to be asserted explicitly with custom matcher.
Otherwise additional restrictions should be asserted manually. It should be:
function getObjectStructure() {
return {
netProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
grossProfits: {
back: expect.any(Number),
lay: expect.any(Number)
},
stakes: {
back: expect.any(Number),
lay: expect.any(Number)
}
}
}
and
expect(result).toBeInstanceOf(Object)
expect(funcB).toHaveBeenCalledTimes(1);
expect(funcB).toHaveBeenCalledWith(
Array(3910).fill(
expect.objectContaining(
getObjectStructure()
)
)
)
const funcBArg = funcB.mock.calls[0][0];
const nonPositiveNetProfitsBack = funcBArg
.map(({ netProfits: { back } }, i) => [i, back])
.filter(([, val] => !(val > 0))
.map(([i, val] => `${netProfits:back:${i}:${val}`);
expect(nonPositiveNetProfitsBack).toEqual([]);
const nonPositiveNetProfitsLay = ...
Where !(val > 0) is necessary to detect NaN. Without custom matcher failed assertion won't result in meaningful message but an index and nonPositiveNetProfitsBack temporary variable name can give enough feedback to spot the problem. An array can be additionally remapped to contain meaningful values like a string and occupy less space in errors.
I'm trying to get some info out of a API call in Nodejs, structured something like a JSON:
{
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
}
}
I'm not sure about the presence of none of these fields(Json could be incomplete).
Is there a better way to get the value of "name" in JSON.event.event_context.sport.name without an ugly if to not get errors like "cannot get field 'sport' of undefined"?
Currently, I'm doing
if(json.event && json.event.event_context && json.event.event_context.sport) {
return json.event.event_context.sport.name;
}
Is there a better way?
Thank you!
what do you mean by saying "I'm not sure about the presence of none of these fields"?
i don't understand what your'e trying to achieve.
Looks like there is also an interesting package that will allow more conditions on searching json :
https://www.npmjs.com/package/jspath
let getNested = (path, obj) => {
return path.split(".").reduce( getPath, obj);
}
let getPath = (path, key) => {
return (path && path[key]) ? path[key] : null
}
let test = {
"foo": "bar",
"baz": { "one": 1, "two": ["to", "too", "two"] },
"event": { "event_context": { "sport": { "name": "soccer" } } }
}
console.log(getNested("none", test))
console.log(getNested("baz.one", test))
console.log(getNested("baz.two", test))
console.log(getNested("event.event_context.sport.name", test))
You can use lodash get to get a potentially deeply-nested value, and also specify a default in case it doesnt exist.
Example
const _ = require('lodash');
const my_object = {
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
};
_.get(my_object, 'event.event_context.sport.name'); // "Soccer"
_.get(my_object, 'event.event_context.sport.nonExistentField', 'default val'); // "default val"
Article: https://medium.com/#appi2393/lodash-get-or-result-f409e73e018b
You can check by using a function to check object keys like :
function checkProperty(checkObject, checkstring){
if(!checkstring)
return false;
var propertiesKeys = checkstring.split('.');
propertiesKeys.forEach(element => {
if(!checkObject|| !checkObject.hasOwnProperty(element)){
return false;
} else {
checkObject= checkObject[element];
}
})
return true;
};
var objectToCheck = {
"generated":"2019-11-04T09:34:11+00:00",
"event":{
"id":"19040956",
"start_":"2019-11-16T11:30:00+00:00",
"event_context":{
"sport":{
"id":"1",
"name":"Soccer"
}
}
}
}
if (checkProperty(objectToCheck ,'event.event_context.sport.name'))
console.log('object to find is : ', objectToCheck .event.event_context.sport.name;)
Yeah there are better ways!
For example, you could use lodash's get() method to reach a nested value.
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
But there is also a native solution.
Currently (11.2019) only Babel can handle this.
I am speaking of Optional chaining. It's new in the Ecmascript world.
Why I like it? Look here!
// Still checks for errors and is much more readable.
const nameLength = db?.user?.name?.length;
What happens when db, user, or name is undefined or null? With the optional chaining operator, JavaScript initializes nameLength to undefined instead of throwing an error.
If you are using Babel as a compiler then you could use it now.
Related link: https://v8.dev/features/optional-chaining
How can I make require one of my options from builder object from command
require('yargs')
.usage('Usage: $0 <cmd> [options]')
.command(
'read',
'Read a note',
{
id: {
demand: true,
string: true
},
first: {
demand: true,
boolean: true
}
},
argv => {
note.read(argv.id).then(data => {
console.log('==================note read==================');
console.log(data);
console.log('==================note read==================');
});
}
)
.help()
.strict().argv;
Here I want user to pass either id or first option for read command
Also when running this command with invalid options it doesn't show errors
node app.js read --id=1 --first=1
yargs: ^12.0.5
You may use check API.
// code is written for logic purpose. Not tested.
.check(function (argv) {
if ((argv.id && !argv.first) || (!argv.id && argv.first)) {
return true;
} else if (argv.id && argv.first) {
throw(new Error('Error: pass either id or first option for read command'));
} else {
throw(new Error('Error: pass either id or first option for read command'));
}
})
PS: 1 can be string or boolean for option values
you can use demandOption = true to solve the problem
This is the solution I am currently using. Though I am looking for a better solution.
require('yargs')
.usage('Usage: $0 <cmd> [options]')
.command(
'read',
'Read a note',
yargs =>
yargs
.option('id', {
string: true
})
.option('first', {
boolean: true
})
.check(({ id, first }) => {
if (!id.trim() && !first) {
throw new Error('id or first option is required');
}
return true
}),
argv => {
if (argv.first) {
note.readFirst().then(data => {
console.log('==================note read==================');
console.log(data);
console.log('==================note read==================');
});
} else {
note.read(argv.id).then(data => {
console.log('==================note read==================');
console.log(data);
console.log('==================note read==================');
});
}
}
)
.help()
.strict().argv;
yargs command takes 4 options. Command, description, builder & handler. Builder can be an object or a function. Using function can be used to provide advanced command specific help.
Also I removed demand for both of them as using demand it will ask both the option but I wanted only one.
Also when setting an option to string or boolean what it does it only casts to that type it doesn't validates the type. So here if no option provided argv.first default value will be false & argv.id default value will be '' empty string.
Also when throwing an Error from check function it actually shows the error message of Error object but if we return false it will show the function body in console as message to help trace the error.
Also without accessing argv yargs will not parse.
see https://yargs.js.org/docs/#api-commandcmd-desc-builder-handler, https://yargs.js.org/docs/#api-argv.