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);
Related
I have component in reactjs-
export const initializeValues=()=>{
if(window.variable1$==undefined || window.variable1$==null)
{
window.variable1$=const.variable1Value;
}
if(window.variable2$==undefined || window.variable2$==null)
{
window.variable2$=const.variable2Value;
}
}
I have written test case in jest for this as -
describe("initializeValues",()=>{
test("Variable1",()=>{
expect(window.variable1$).toBeUndefined();
});
test("Variable2",()=>{
expect(window.variable2$).toBeUndefined();
});
});
The test cases written above runs successfully.
But it shows only 5% of code coverage.
How can I add more test cases when function is not returning anything? (with just assignment of values)
I am not quite sure what const.variable1Value is but anyway.
In the test you should prepare the test preconditions and run the tested function.
You can check in coverage report what actually is covered. There are information about e.g. else path not taken and you must also cover that.
In this case following tests are needed:
for the first condition:
// covers first part of first if statement
it("should set variable1 if undefined", () => {
window.variable1 = undefined;
initializeValues();
expect(window.variable1).toEqual(variable1Value)
});
// covers second part of first if statement
it("should set variable1 if null", () => {
window.variable1 = null;
initializeValues();
expect(window.variable1).toEqual(variable1Value)
});
// covers else part of first if statement
it("should not set variable1 if already set", () => {
window.variable1 = "some value different than variable1Value";
initializeValues();
expect(window.variable1).toEqual("some value different than variable1Value")
});
Same set for the second if statement.
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.
when performing unit testing is it better to test the literal result that I expect hard-coding it into the test (expect(x).toBe(17)), or is it better to test the logic and not the specific mock data that I am using (expect(x).toBe(mockedData.value))
The first approach seems safer because I am sure that the test is actually testing the literal result that I expect, however the second approach is more flexible since it allows me to test the logic rather than worry about the mock data (which I can also change later without having to rewrite the test itself)
What are the advantages/disadvantages of either approach? What is the best practice in these cases?
Following is a quick example:
// MockedData is a very long array of complex objects
// each of them has a property 'value' of type number
import mockedData from 'data.mock';
class ClassToTest {
private data;
constructor(data) {
this.data = data;
}
plusOne(): number {
return this.data.value + 1;
}
}
describe('test', () => {
let instance: ClassToTest;
beforeEach(() => {
instance = new ClassToTest(mockedData[0]);
})
it('plusOne() should return the property "value" plus one', () => {
// Should I write this...
expect(instance.plusOne()).toBe(mockedData[0] + 1);
// ...or this?
expect(instance.plusOne()).toBe(17); // Because I know that mockedData[0].value is 16
})
});
Thank you very much!! :)
In your test you want to test your unit, which in your case is the logic inside of your plusOne() function. So you want to only know if something changes inside the function.
The most dangerous path is to use expect(instance.plusOne()).toBe(17);, because if someone changes your logic to return this.data.value + 2;, you will never spot from test only if the problem is in the function logic or in the mockedData.
The less dangerous approach is to use expect(instance.plusOne()).toBe(mockedData[0] + 1);, because this will tell you if the logic in your function change. Still not optimal, since you depend on an external mock to run your test that you don't need. Why would you want to depend on an external mocked data to test your unit?
The best way to test your unit logic here is to do something like this:
describe('test', () => {
let instance: ClassToTest;
const mockedValue = 1;
beforeEach(() => {
instance = new ClassToTest(mockedValue);
})
it('plusOne() should return the property "value" plus one', () => {
expect(instance.plusOne()).toBe(mockedValue + 1);
})
});
Then, you can implement separate tests for your service, here you only test the logic inside plusOne().
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>