Im having some issues to understand testing, since I almost never find it neccessary.
If I have simple functions like
function isMovementOutOfBounds(newPosition) {
if (newPosition[0] > input[0][0] || newPosition[0] < 0 || newPosition[1] > input[0][1] || newPosition[1] < 0) {
return true;
}
return false;
}
or
function isMovementForbidden(forbiddenMovements, initialPosition, newPosition) {
function isArrayInArray(arr, item) {
const item_as_string = JSON.stringify(item);
const contains = arr.some((ele) => JSON.stringify(ele) === item_as_string);
return contains;
}
const newMovement = [initialPosition, newPosition];
if (isArrayInArray(forbiddenMovements, newMovement)) {
return true;
}
return false;
}
Should they even be tested? They have always a return, and its always boolean. So I don't understand if its really necessary to test it.
Maybe I should test for the type of input they receive?
It all seems dumb to me, how could I test those functions? Any idea of what should I look for?
At the very least
testing helps you to make sure that your code behaves as expected under different circumstances (all kinds of inputs, including edge cases)
testing may help you to maintain touch points between your specific module and other parts of your project
be sure that while you develop your app it still fulfills your basic requirements
Just to give you a clue, as to which kind of tests you may perform, check out the following demo (which shows that your code is not functional, as of now):
mocha.setup('bdd')
const { expect } = chai
function isMovementForbidden(forbiddenMovements, initialPosition, newPosition) {
function isArrayInArray(arr, item) {
const item_as_string = JSON.stringify(item);
const contains = arr.some((ele) => JSON.stringify(ele) === item_as_string);
return contains;
}
const newMovement = [initialPosition, newPosition];
if (isArrayInArray(forbiddenMovements, newMovement)) {
return true;
}
return false;
}
const testSuite = [
{
descr: 'Should work for basic coordinates',
input: [[[0,0],[1,1]], [2,3], [1,1]],
output: true
},
{
descr: 'Should be able to handle empty array of forbidden movements',
input: [[], [0,0], [1,1]],
output: false
},
{
descr: 'Should be able to allow staying at current point',
input: [[1,1], [0,0], [0,0]],
output: false
}
]
describe('Basic test', ()=>{
testSuite.forEach(({input, output, descr}) =>
it(descr, ()=>{
expect(isMovementForbidden(...input)).to.equal(output)
}))
})
mocha.run()
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.0.1/mocha.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.2.0/chai.min.js"></script><div id="mocha"></div>
Testing has many benefits. You may detect unnecessary code or ideally find some errors.
In this case you could test that your functions return the correct boolean, given some edge cases or some basic input.
At least one or two simple test are enough to test the function itself, if you don't want to test too much.
Related
I have a js file that looks for paths and rewrites the paths, and it has over 300 if/else statements. I am looking for ways to clean it up, but I also need to make sure that it is readable for the next person who will work on it. My first thought was to convert all of these if statements to either ternaries or switch statements, but I am not sure if that is the best approach. I am trying to balance the urge to write "better" code, but also I need to make sure it stays readable, and the current if/else structure reads pretty well.
Example:
if (request.uri.match(^\/path1\/.*$)) {
request.uri = request.uri.replace(/^\/path1\/(.*)$/) "newPath" + "$1");
} else if (request.uri.match(^\/path2\/.*$)) {
request.uri = request.uri.replace(/^\/path2\/(.*)$/) "newPath" + "$1");
} else if (request.uri.match(^\/path3\/.*$)) {
request.uri = request.uri.replace(/^\/path3\/(.*)$/) "newPath" + "$1");
}
I would say about 75% of the 300 if statements look similar. The path1, path2 examples are overly simplistic, the regex for each of these range from simple to complex. The other 25% incorporate other logic aside from just rewriting the URL. Think injecting headers, or in some cases one more level of nested if/else statements.
Here is an example of what some of those might look like:
else if (request.uri.match(^\/path3(-new)?\/.*$)) {
request.headers['header'] = {"value": "something"};
if (request.uri.match(^\/path3\/.*$)) {
request.uri = request.uri.replace(/^\/path3\/(.*)$/) "/newPathA/" + "$1");
} else if (request.uri.match(^\/path3-new\/.*$)) {
request.uri = request.uri.replace(/^\/path3-new\/(.*)$/) "/newPathB/" + "$1");
}
}
I went down the path to build functions to handle different things, like the uri replace lines. The challenge is the code that the functions are replacing aren't difficult, and it just feels like a bit of a waste. I can either have a line that does this:
request.uri = request.uri.replace(/^\/path3-new\/(.*)$/) "/newPathB/" + "$1");
Or I can have this:
function pathModifier(newPath) {
request.uri = reqUri.replace(/^(\/.*?[\/])(.*)$/, newPath);
return;
}
pathRewriter("/newPathA/" + "$1");
I am not saving a lot, and it makes it more difficult to read for the person I pass this on to.
So, what would you do?
I'd use a declarative approach for the majority of cases and still expose a way to extend it by passing a custom function.
This way you have low amounts of repetition in your code while still offering the needed flexibility.
Something like this:
const rewrites = {
'/path1/': ['/newPathA/'],
'/path2/': ['/newPathB/'],
'/path3/': ['/newPathC1/', { headers: { name: 'value' } }],
'/path3-new/': ['/newPathC2/', { headers: { name: 'value' } }],
'/path4': ['/newPathD', { headers: { name: 'value' }, callback: req => {
req.customProperty = 123
req.doCustomStuff()
} }]
}
function handleRewrites (req, rewrites) {
const [newPath, options] = Object.keys(rewrites)
.find(oldPath => req.uri.startsWith(oldPath)) ?? []
if (newPath) {
req.uri = newPath + req.uri.slice(oldPath.length)
if (options?.headers) Object.assign(req.headers, options.headers)
options?.callback?.(req)
}
}
Of course you are free to design the "domain-specific language" of how to declare the rewrites as it best fits your use case. I chose an object with old path as key and [newPath, optionalOptionsObject] as value because the majority of the time I'd expect there to be just an oldPath=>newPath transformation but it could be different of course.
If you would need for example actual regular expressions in some cases, you could of course change it and do something like an array where you then use values of the format [oldPathOrRegex, newPath, optionalOptionsObject]. You could then allow the easy string path but also allow regexes (depending on whether it is instanceof RegExp you'd apply one logic or the other, allowing you to specify both types of path selectors with little effort when declaring the rewrites).
There is of course still the thing that you would specify the common headers of several routes multiple times. One of the possible ways to allow specifying a (possibly even nested) group of rewrites with common properties that get merged with any per-route properties would be creating a function that takes the common settings plus another rewrite list and returns an expanded version of each given rewrite, and to then spread the result of the function with ...:
function withOptions (commonOptions, rewrites) {
for (const rewrite of Object.values(rewrites)) {
if (!rewrite[1]) rewrite[1] = {}
const rewriteOptions = rewrite[1]
if (commonOptions.headers || rewriteOptions.headers) {
rewriteOptions.headers = {
...commonOptions.headers,
...rewriteOptions.headers
}
}
if (commonOptions.callback || rewriteOptions.callback) {
const childCallback = rewriteOptions.callback
rewriteOptions.callback = req => {
commonOptions.callback?.(req)
childCallback?.(req)
}
}
}
return rewrites
}
const rewrites = {
'/path1/': ['/newPathA/'],
'/path2/': ['/newPathB/'],
...withOptions({ headers: { name: 'value' } }, {
'/path3': ['/newPathC2/'],
'/path3-new': ['/newPathC3/']
}),
'/path4': ['/newPathD', { headers: { name: 'value' }, callback: req => {
req.customProperty = 123
req.doCustomStuff()
} }]
}
Note that in this example I wrote withOptions so it mutates the given rewrites, because it was the simplest way and I assumed it would be passed object literals anyway. If this isn't true, it can be changed to return copies of course.
If you have many cases, I would suggest using a HashMap/Object, to map between the lookup key and their value.
const paths = {
'/old/path/to/a': '/new/path/to/a',
'/old/path/to/b': '/new/path/to/b',
'/old/path/to/c': '/new/path/to/c'
}
function getNewPath(oldPath) {
return paths[oldPath] || '/fallback/not/found'
}
Or if you have to handle different business logic, you could also return a function
const paths = {
'/old/path/to/a': () => { return modifyA() },
'/old/path/to/b': () => { return modifyB() },
'/old/path/to/c': () => { return modifyC() },
}
function getNewPath(oldPath) {
return paths[oldPath]()
}
I need to test my JSON response to ensure at least 1 object contains a value of isKey:true at which point a global variable of hasKey is set to true.
I believe the SOME method would help in this situation but it seems to only test on a local level so if I console.log I get: true,false, true,true... etc
I just want a definitive true or false against the whole model.
Below you can see the basis of a working function, but I don't believe it is efficient so any advice is appreciated in improving this.
checkKeys() {
let checkTest: boolean = false;
this.modalData.columnPermissions.some(function (item) {
if (item.isKey) {
checkTest = true;
}
});
this.modalData.hasKey = checkTest;
}
You could assign the result of some directly.
checkKeys() {
this.modalData.hasKey = this.modalData.columnPermissions.some(function (item) {
return item.isKey;
});
}
You've got the right function, you're just using it wrong.
this.modalData.hasKey = this.modalData.columnPermissions.some(function (item) {
return item.isKey;
});
The 'some' function takes the return value and STOPS RUNNING as soon as one is true.
The 'every' function takes the return value and stops running as soon as one is false.
It will be the best explain in on example
expected(someNumber).toBe(1).or.toBe(-2).or.toBe(22) // expect result is 1 or -2 or 22
This is bad syntax, but can do sth like that in jest?
A simple way around this is to use the standard .toContain() matcher (https://jestjs.io/docs/en/expect#tocontainitem) and reverse the expect statement:
expect([1, -2, 22]).toContain(someNumber);
If you really needed to do exactly that, I suppose you could put the logical comparisons inside the expect call, e.g.
expect(someNumber === 1 || someNumber === -2 || someNumber === 22).toBeTruthy();
If this is just for a "quick and dirty" check, this might suffice.
However, as suggested by several comments under your question, there seem to be several "code smells" that make both your initial problem as well as the above solution seem like an inappropriate way of conducting a test.
First, in terms of my proposed solution, that use of toBeTruthy is a corruption of the way Jasmine/Jest matchers are meant to be used. It's a bit like using expect(someNumber === 42).toBeTruthy(); instead of expect(someNumber).toBe(42). The structure of Jest/Jasmine tests is to provide the actual value in the expect call (i.e. expect(actualValue)) and the expected value in the matcher (e.g. toBe(expectedValue) or toBeTruthy() where expectedValue and true are the expected values respectively). In the case above, the actual value is (inappropriately) provided in the expect call, with the toBeTruthy matcher simply verifying this fact.
It might be that you need to separate your tests. For example, perhaps you have a function (e.g. called yourFunction) that you are testing that provides (at least) 3 different possible discrete outputs. I would presume that the value of the output depends on the value of the input. If that is the case, you should probably test all input/output combinations separately, e.g.
it('should return 1 for "input A" ', () => {
const someNumber = yourFunction("input A");
expect(someNumber).toBe(1);
});
it('should return -2 for "input B" ', () => {
const someNumber = yourFunction("input B");
expect(someNumber).toBe(-2);
});
it('should return 22 for "input C" ', () => {
const someNumber = yourFunction("input C");
expect(someNumber).toBe(22);
});
..or at least...
it('should return the appropriate values for the appropriate input ', () => {
let someNumber;
someNumber = yourFunction("input A");
expect(someNumber).toBe(1);
someNumber = yourFunction("input B");
expect(someNumber).toBe(-2);
someNumber = yourFunction("input C");
expect(someNumber).toBe(22);
});
One of the positive consequences of doing this is that, if your code changes in the future such that, e.g. one (but only one) of the conditions changes (in terms of either input or output), you only need to update one of three simpler tests instead of the single more complicated aggregate test. Additionally, with the tests separated this way, a failing test will more quickly tell you exactly where the problem is, e.g. with "input A", "input B", or "input C".
Alternatively, you may need to actually refactor yourFunction, i.e. the code-under-test itself. Do you really want to have a particular function in your code returning three separate discrete values depending on different input? Perhaps so, but I would examine the code separately to see if it needs to be re-written. It's hard to comment on this further without knowing more details about yourFunction.
To avoid putting all the logical comparisons in one statement and using toBeTruthy(), you can use nested try/catch statements:
try {
expect(someNumber).toBe(1)
}
catch{
try {
expect(someNumber).toBe(-2)
}
catch{
expect(someNumber).toBe(22)
}
}
To make it more convenient and more readable, you can put this into a helper function:
function expect_or(...tests) {
if (!tests || !Array.isArray(tests)) return;
try {
tests.shift()?.();
} catch (e) {
if (tests.length) expect_or(...tests);
else throw e;
}
}
NB: With Typescript replace line 1 with function expect_or(...tests: (() => void)[]) { to add types to the function parameter.
and use it like this:
expect_or(
() => expect(someNumber).toBe(1),
() => expect(someNumber).toBe(-2),
() => expect(someNumber).toBe(22)
);
As #JrGiant suggested, there could be a toBeOneOf, however, it is easy top implement your own matcher:
Example in TypeScript:
expect.extend({
toBeOneOf(received: any, items: Array<any>) {
const pass = items.includes(received);
const message = () =>
`expected ${received} to be contained in array [${items}]`;
if (pass) {
return {
message,
pass: true
};
}
return {
message,
pass: false
};
}
});
// Declare that jest contains toBeOneOf
// If you are not using TypeScript, remove this "declare global" altogether
declare global {
namespace jest {
interface Matchers<R> {
toBeOneOf(items: Array<any>): CustomMatcherResult;
}
}
}
describe("arrays", () => {
describe("getRandomItemFromArray", () => {
it("should return one of the expected - 1", () => {
expect(getRandomItemFromArray([1, 2])).toBeOneOf([1, 2])
});
});
});
I was also looking for a solution for the expect.oneOf issue. You may want to checkout d4nyll's solution.
Here is an example of how it could work.
expect(myfunction()).toBeOneOf([1, -2, 22]);
I recommend using the .toContain(item) matcher. The documentation can be found here.
The below code should work well:
expect([1, -2, 22]).toContain(someNumber);
In one of our tests, we have the following set of expectations:
expect(headerPage.dashboard.isDisplayed()).toBe(true);
expect(headerPage.queue.isDisplayed()).toBe(true);
expect(headerPage.claimSearch.isDisplayed()).toBe(true);
expect(headerPage.claim.isDisplayed()).toBe(true);
expect(headerPage.case.isDisplayed()).toBe(true);
expect(headerPage.calendar.isDisplayed()).toBe(true);
On one hand, having multiple simple expectations provide a more precise and understandable feedback, but, on another, this looks like it violates the DRY principle and the "one expectation per test" commonly acceptable guideline.
Is there a way to convert/simplify it to a single expect?
headerPage is a Page Object, dashboard and other page object fields are navigation links.
I think you've misunderstood the purpose of the "one expectation per test" guideline. The point isn't to combine a bunch of expectations into a single expectation, it's to split up your expectations into separate tests.
To follow the spirit of that guideline, you would write your tests like this:
describe("The header page", function () {
var headerPage;
beforeEach(function () {
//Common logic here
});
it("displays the dashboard", function () {
expect(headerPage.dashboard.isDisplayed()).toBe(true);
});
it("displays the queue", function () {
expect(headerPage.queue.isDisplayed()).toBe(true);
});
it("displays the claimSearch", function () {
expect(headerPage.claimSearch.isDisplayed()).toBe(true);
});
//etc.
});
That's a fair bit more verbose than what you have; but that's why these are guidelines not rules. It's a tradeoff between how verbose you make your tests, versus how easy they are to debug later. ("The header page displays the dashboard: FAILED") is a very clear and specific test failure message, compared to getting the same failure message regardless of which expectation actually failed.
I definitely would not try to combine all of these lines into a single line. If you don't want to split it into a bunch of different test cases I would just leave it how it is.
Alternative approach. What I've ended up with was to add a page object method that returns the labels of the currently visible navigation links:
this.getVisibleLinks = function () {
return $$(".ap-header-nav-tabs li a").filter(function (link) {
return link.isDisplayed();
}).getText();
};
Then, the above test would be transformed to a concise and readable:
expect(headerPage.getVisibleLinks()).toEqual(["Dashboard", "Queue", "Claim Search", ...]);
If this is logic you are using across multiple specs, you could look into jasmine custom matchers to encapsulate the logic.
It would be written somewhat like this:
var customMatchers = {
toDisplayWidgets: function(util, customEqualityTests) {
return {
compare: function(actual, expected) {
function isDisplayingWidgets(page) {
return page.dashboard.isDisplayed() &&
page.queue.isDisplayed() &&
page.claimSearch.isDisplayed() &&
page.claim.isDisplayed() &&
page.case.isDisplayed() &&
page.calendar.isDisplayed();
}
var result = {};
result.pass = isDisplayingWidgets(actual);
if (!result.pass) {
result.message = 'dashboard is not displayed';
}
return result;
}
}
}
Add the matcher to your current test
jasmine.addMatchers(customMatchers);
And then in your tests you could just assert with
expect(headerPage).toDisplayWidgets();
what about using a helper function that returns results of all the tests, something like
expect(headerDisplayTests()).toBe(true);
function headerDisplayTests() {
return headerPage.dashboard.isDisplayed() &&
headerPage.queue.isDisplayed() &&
headerPage.claimSearch.isDisplayed() &&
headerPage.claim.isDisplayed() &&
headerPage.case.isDisplayed() &&
headerPage.calendar.isDisplayed();
}
I use the following code which is working great but I wonder if in JS there is a way to avoid the if and to do it inside the loop, I want to use also lodash if it helps
for (provider in config.providers[0]) {
if (provider === "save") {
....
You can chain calls together using _.chain, filter by a value, and then use each to call a function for each filtered result. However, you have to add a final .value() call at the end for it to evaluate the expression you just built.
I'd argue that for short, simple conditional blocks, an if statement is easier and more readable. I'd use lodash- and more specifically chaining- if you are combining multiple operations or performing sophisticated filtering, sorting, etc. over an object or collection.
var providers = ['hello', 'world', 'save'];
_.chain(providers)
.filter(function(provider) {
return provider === 'save';
}).each(function(p) {
document.write(p); // your code here
}).value();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.js"></script>
Edit: My mistake; filter does not have an overload where you can just supply a literal value. If you want to do literal value checking you have to supply a function as in my amended answer above.
I'd argue that what you have there is pretty good, clean and readable, but since you mentioned lodash, I will give it a try.
_.each(_.filter(config.providers[0], p => p === 'save'), p => {
// Do something with p
...
});
Note that the arrow function/lambda of ECMAScript 6 doesn't come to Chrome until version 45.
Basically, you are testing to see if config.providers[0], which is an object, contains a property called save (or some other dynamic value, I'm using a variable called provider to store that value in my example code below).
You can use this instead of using a for .. in .. loop:
var provider = 'save';
if (config.providers[0][provider] !== undefined) {
...
}
Or using #initialxy's (better!) suggestion:
if (provider in config.providers[0]) {
...
}
How about:
for (provider in config.providers[0].filter(function(a) {return a === "save"}) {
...
}
Strategy, you are looking for some kind of strategy pattern as,
Currenlty the save is hardcoded but what will you do if its coming from other varible – Al Bundy
var actions = {
save: function() {
alert('saved with args: ' + JSON.stringify(arguments))
},
delete: function() {
alert('deleted')
},
default: function() {
alert('action not supported')
}
}
var config = {
providers: [{
'save': function() {
return {
action: 'save',
args: 'some arguments'
}
},
notSupported: function() {}
}]
}
for (provider in config.providers[0]) {
(actions[provider] || actions['default'])(config.providers[0][provider]())
}
Push „Run code snippet” button will shows two pop-ups - be carefull
It is not clearly stated by the original poster whether the desired output
should be a single save - or an array containing all occurrences of
save.
This answer shows a solution to the latter case.
const providers = ['save', 'hello', 'world', 'save'];
const saves = [];
_.forEach(_.filter(providers, elem => { return elem==='save' }),
provider => { saves.push(provider); });
console.log(saves);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.js"></script>