How to debug Jest unit test for a debounced function - reactjs

I have the following function:
myFunction = _.debounce((viewport) => {
...
otherFunction();
...
someOtherFunction();
...
}, 650)
I'm trying to use jest to assert whether the otherFunction and someOtherFunction were run. In order to do this, I wanted to use WebStorm's debugger to check the function flow.
I've tried mocking the _.debounce:
_.debounce = jest.fn((fn) => fn);
Even though I tried adding a spy on the myFunction and jest says that the function ran, I can't make the debugger stop inside the function.
If I try to remove the _.debounce function, the debugger stops successfully.
Is there any way to make this work?

I ended up moving my function logic to a new function: myFunctionNoDebounce and I kept the old myFunction only as a debounce wrapper. This way, I can test the myFunctionNoDebounce and I don't really need to test the myFunction.
This is certainly not the most elegant solution, but it works for now. I'm open to more suggestions to solve this problem in a better way.
myFunctionNoDebounce = (viewport) => {
...
otherFunction();
...
someOtherFunction();
...
}
myFunction = _.debounce((viewport) => {
this.myFunctionNoDebounce(viewport)
}, 650)

This is untested, but I believe you would have to execute the function in your mock implementation:
_.debounce = jest.fn(fn => fn());

Related

react: helping to understand specific currying use case

<Select
onChange={evt => myFunction('KEY', ['ARRAY', 'OF', 'VALUES'])(evt)}
...
const myFunction = (key, funValues) => {
return (evt: React.ChangeEvent<HTMLSelectElement>) => {
const { values } = evt.target;
if (funValues.find( some condition) ){
callAPI(key, funValues);
}
else{
callAPI(key, values);
}
};
};
I would have written this simply as
onChange={evt => myFunction('KEY', ['ARRAY', 'OF', 'VALUES'], evt)}
I am really failing to see what was the logic of applying currying here, and how it will make
a) this operation better
and/or
b) contributes to broader benefits
Maybe this helps with context, but myFunction is called throughout the codebase. Sometimes via an evt, and sometimes manually.
Creating a curried function (I guess) is a misunderstanding for avoiding an arrow function in the callback handler. In order to this misunderstanding some people define this function as a curried one and use such as:
onChange={myFunction('KEY', ['ARRAY', 'OF', 'VALUES'])}
As you can see this is somehow shorter than your version and works. So, you don't have to use an arrow function and invoke it as you do. event is passed again.
Some people think that this avoids a recreation of this function in each render, but that is not true.

Modal Enzyme mount unit test: MutationObserver is not defined [duplicate]

I wrote a script with the main purpose of adding new elements to some table's cells.
The test is done with something like that:
document.body.innerHTML = `
<body>
<div id="${containerID}">
<table>
<tr id="meta-1"><td> </td></tr>
<tr id="meta-2"><td> </td></tr>
<tr id="meta-3"><td> </td></tr>
<tr id="no-meta-1"><td> </td></tr>
</table>
</div>
</body>
`;
const element = document.querySelector(`#${containerID}`);
const subject = new WPMLCFInfoHelper(containerID);
subject.addInfo();
expect(mockWPMLCFInfoInit).toHaveBeenCalledTimes(3);
mockWPMLCFInfoInit, when called, is what tells me that the element has been added to the cell.
Part of the code is using MutationObserver to call again mockWPMLCFInfoInit when a new row is added to a table:
new MutationObserver((mutations) => {
mutations.map((mutation) => {
mutation.addedNodes && Array.from(mutation.addedNodes).filter((node) => {
console.log('New row added');
return node.tagName.toLowerCase() === 'tr';
}).map((element) => WPMLCFInfoHelper.addInfo(element))
});
}).observe(metasTable, {
subtree: true,
childList: true
});
WPMLCFInfoHelper.addInfo is the real version of mockWPMLCFInfoInit (which is a mocked method, of course).
From the above test, if add something like that...
const table = element.querySelector(`table`);
var row = table.insertRow(0);
console.log('New row added'); never gets called.
To be sure, I've also tried adding the required cells in the new row.
Of course, a manual test is telling me that the code works.
Searching around, my understanding is that MutationObserver is not supported and there is no plan to support it.
Fair enough, but in this case, how can I test this part of my code? Except manually, that is :)
I know I'm late to the party here, but in my jest setup file, I simply added the following mock MutationObserver class.
global.MutationObserver = class {
constructor(callback) {}
disconnect() {}
observe(element, initObject) {}
};
This obviously won't allow you to test that the observer does what you want, but will allow the rest of your code's tests to run which is the path to a working solution.
I think a fair portion of the solution is just a mindset shift. Unit tests shouldn't determine whether MutationObserver is working properly. Assume that it is, and mock the pieces of it that your code leverages.
Simply extract your callback function so it can be tested independently; then, mock MutationObserver (as in samuraiseoul's answer) to prevent errors. Pass a mocked MutationRecord list to your callback and test that the outcome is expected.
That said, using Jest mock functions to mock MutationObserver and its observe() and disconnect() methods would at least allow you to check the number of MutationObserver instances that have been created and whether the methods have been called at expected times.
const mutationObserverMock = jest.fn(function MutationObserver(callback) {
this.observe = jest.fn();
this.disconnect = jest.fn();
// Optionally add a trigger() method to manually trigger a change
this.trigger = (mockedMutationsList) => {
callback(mockedMutationsList, this);
};
});
global.MutationObserver = mutationObserverMock;
it('your test case', () => {
// after new MutationObserver() is called in your code
expect(mutationObserverMock.mock.instances).toBe(1);
const [observerInstance] = mutationObserverMock.mock.instances;
expect(observerInstance.observe).toHaveBeenCalledTimes(1);
});
The problem is actually appears because of JSDom doesn't support MutationObserver, so you have to provide an appropriate polyfill.
Little tricky thought may not the best solution (let's use library intend for compatibility with IE9-10).
you can take opensource project like this one https://github.com/webmodules/mutation-observer which represents similar logic
import to your test file and make global
Step 1 (install this library to devDependencies)
npm install --save-dev mutation-observer
Step 2 (Import and make global)
import MutationObserver from 'mutation-observer'
global.MutationObserver = MutationObserver
test('your test case', () => {
...
})
You can use mutationobserver-shim.
Add this in setup.js
import "mutationobserver-shim"
and install
npm i -D mutationobserver-shim
Since it's not mentioned here: jsdom has supported MutationObserver for a while now.
Here's the PR implementing it https://github.com/jsdom/jsdom/pull/2398
This is a typescript rewrite of Matt's answer above.
// Test setup
const mutationObserverMock = jest
.fn<MutationObserver, [MutationCallback]>()
.mockImplementation(() => {
return {
observe: jest.fn(),
disconnect: jest.fn(),
takeRecords: jest.fn(),
};
});
global.MutationObserver = mutationObserverMock;
// Usage
new MutationObserver(() => {
console.log("lol");
}).observe(document, {});
// Test
const observerCb = mutationObserverMock.mock.calls[0][0];
observerCb([], mutationObserverMock.mock.instances[0]);
Addition for TypeScript users:
declare the module with adding a file called: mutation-observer.d.ts
/// <reference path="../../node_modules/mutation-observer" />
declare module "mutation-observer";
Then in your jest file.
import MutationObserver from 'mutation-observer'
(global as any).MutationObserver = MutationObserver
Recently I had a similar problem, where I wanted to assert on something that should be set by MutationObserver and I think I found fairly simple solution.
I made my test method async and added await new Promise(process.nextTick); just before my assertion. It puts the new promise at the end on microtask queue and holds the test execution until it is resolved. This allows for the MutationObserver callback, which was put on the microtask queue before our promise, to be executed and make changes that we expect.
So in general the test should look somewhat like this:
it('my test', async () => {
somethingThatTriggersMutationObserver();
await new Promise(process.nextTick);
expect(mock).toHaveBeenCalledTimes(3);
});

How do you test for the non-existence of an element using jest and react-testing-library?

I have a component library that I'm writing unit tests for using Jest and react-testing-library. Based on certain props or events I want to verify that certain elements aren't being rendered.
getByText, getByTestId, etc throw and error in react-testing-library if the element isn't found causing the test to fail before the expect function fires.
How do you test for something not existing in jest using react-testing-library?
From DOM Testing-library Docs - Appearance and Disappearance
Asserting elements are not present
The standard getBy methods throw an error when they can't find an element, so
if you want to make an assertion that an element is not present in the DOM,
you can use queryBy APIs instead:
const submitButton = screen.queryByText('submit')
expect(submitButton).toBeNull() // it doesn't exist
The queryAll APIs version return an array of matching nodes. The length of the
array can be useful for assertions after elements are added or removed from the
DOM.
const submitButtons = screen.queryAllByText('submit')
expect(submitButtons).toHaveLength(2) // expect 2 elements
not.toBeInTheDocument
The jest-dom utility library provides the
.toBeInTheDocument() matcher, which can be used to assert that an element is
in the body of the document, or not. This can be more meaningful than asserting
a query result is null.
import '#testing-library/jest-dom/extend-expect'
// use `queryBy` to avoid throwing an error with `getBy`
const submitButton = screen.queryByText('submit')
expect(submitButton).not.toBeInTheDocument()
Use queryBy / queryAllBy.
As you say, getBy* and getAllBy* throw an error if nothing is found.
However, the equivalent methods queryBy* and queryAllBy* instead return null or []:
queryBy
queryBy* queries return the first matching node for a query, and return null if no elements match. This is useful for asserting an element that is not present. This throws if more than one match is found (use queryAllBy instead).
queryAllBy
queryAllBy* queries return an array of all matching nodes for a query, and return an empty array ([]) if no elements match.
https://testing-library.com/docs/dom-testing-library/api-queries#queryby
So for the specific two you mentioned, you'd instead use queryByText and queryByTestId, but these work for all queries, not just those two.
getBy* throws an error when not finding an elements, so you can check for that
expect(() => getByText('your text')).toThrow('Unable to find an element');
const submitButton = screen.queryByText('submit')
expect(submitButton).toBeNull() // it doesn't exist
expect(submitButton).not.toBeNull() // it exist
You have to use queryByTestId instead of getByTestId.
Here a code example where i want to test if the component with "car" id isn't existing.
describe('And there is no car', () => {
it('Should not display car mark', () => {
const props = {
...defaultProps,
base: null,
}
const { queryByTestId } = render(
<IntlProvider locale="fr" messages={fr}>
<CarContainer{...props} />
</IntlProvider>,
);
expect(queryByTestId(/car/)).toBeNull();
});
});
Worked out for me (if you want to use getByTestId):
expect(() => getByTestId('time-label')).toThrow()
Hope this will be helpfull
this table shows why/when function errors
which functions are asynchronous
what is return statement for function
Another solution: you could also use a try/catch block
expect.assertions(1)
try {
// if the element is found, the following expect will fail the test
expect(getByTestId('your-test-id')).not.toBeVisible();
} catch (error) {
// otherwise, the expect will throw, and the following expect will pass the test
expect(true).toBeTruthy();
}
You can use react-native-testing-library "getAllByType" and then check to see if the component is null. Has the advantage of not having to set TestID, also should work with third party components
it('should contain Customer component', () => {
const component = render(<Details/>);
const customerComponent = component.getAllByType(Customer);
expect(customerComponent).not.toBeNull();
});
// check if modal can be open
const openModalBtn = await screen.findByTestId("open-modal-btn");
fireEvent.click(openModalBtn);
expect(
await screen.findByTestId(`title-modal`)
).toBeInTheDocument();
// check if modal can be close
const closeModalBtn = await screen.findByTestId(
"close-modal-btn"
);
fireEvent.click(closeModalBtn);
const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
await sleep(500);
expect(screen.queryByTestId("title-modal")).toBeNull();
I recently wrote a method to check visibility of element for a jest cucumber project.
Hope it is useful.
public async checknotVisibility(page:Page,location:string) :Promise<void>
{
const element = await page.waitForSelector(location);
expect(element).not.toBe(location);
}
don't want to bury the lead, so here's the right solution ✅
waitFor(() => queryByTestId(/car/) === null)
There are issues with all of the answers here so far...
don't use getByTestId, that'll have to wait 😴 for the timeout because it's expecting the element to eventually be there. Then it'll throw and you'll have to catch that, which is a less readable test. Finally you could have a RACE CONDITION 🚫 where getByTestId is evaluated before the element disappears and our test will flake.
Just using queryByTestId without waitFor is a problem if the page is changing at all and the element has not disappeared yet. RACE CONDITION 🚫
deleteCarButton.click();
expect(queryByTestId(/car/)).toBeNull(); //
if expect() gets evaluated before the click handler and render completes we'll have a bad time.
The default behavior of queryByRole is to find exactly one element. If not, it throws an error. So if you catch an error, this means the current query finds 0 element
expect(
()=>screen.getByRole('button')
).toThrow()
getByRole returns 'null', if it does not find anthing
expect(screen.queryByRole('button')).toEqual((null))
findByRole runs asynchronously, so it returns a Promise. If it does not find an element, it rejects the promise. If you are using this, you need to run async callback
test("testing", async () => {
let nonExist = false;
try {
await screen.findByRole("button");
} catch (error) {
nonExist = true;
}
expect(nonExist).toEqual(true);
});

Testing scrollintoview Jest

I have a simple function:
scrollToSecondPart() {
this.nextPartRef.current.scrollIntoView({ behavior: "smooth" });
}
and I would like to test it using Jest. When I call my function I have this error:
TypeError: Cannot read property 'scrollIntoView' of null
The code works great in the application but not in the unit test.
Here is the unit test:
it("should scroll to the second block", () => {
const scrollToSecondPart = jest.spyOn(LandingPage.prototype, 'scrollToSecondPart');
const wrapper = shallow(<LandingPage />);
const instance = wrapper.instance();
instance.scrollToSecondPart();
expect(scrollToSecondPart).toHaveBeenCalled();
});
I guess the problem is that the unit test can't access to this.nextPartRef but I don't know how I should mock this element.
By the way, I'm using "Refs" has described in https://reactjs.org/docs/refs-and-the-dom.html (I'm using React.createRef()).
Thank you!
So I stumbled on this question because I thought it was about how to test Element.scrollIntoView(), which can be done by mocking it with jest as follows:
let scrollIntoViewMock = jest.fn();
window.HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
<execute application code that triggers scrollIntoView>
expect(scrollIntoViewMock).toBeCalled();
However, that does not seem to be your goal. In your test you are calling scrollToSecondPart() in your test and then expect(scrollToSecondPart).toHaveBeenCalled() which is essentially testing that scrollToSecondPart is a function of LandingPage or am I missing something?
for me i had to mock the scrollIntoView for elements like this:
const scrollIntoViewMock = jest.fn();
Element.prototype.scrollIntoView = scrollIntoViewMock()
as found here:
https://github.com/jsdom/jsdom/issues/1695#issuecomment-449931788

[reactjs]Anonymous function definitions not equivalent

I am new to React. I encountered a strange problem while I am following the "Quick Start" of React official page https://facebook.github.io/react/docs/state-and-lifecycle.html . You can try on CodePen here: http://codepen.io/gaearon/pen/amqdNA?editors=0010
In this code snippet
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
it uses arrow function syntax to define a function which would be executed repeatedly, by passing the anonymous function object as the first argument of setInterval().
Since I am a curious guy, I tried several different ways to pass an "equivalent" function object as the first argument. But none of my ways works.
componentDidMount() {
this.timerID = setInterval(
this.tick, // This cannot work in CodePen
//function () {
//this.tick();
//}, // No
//function () {
//return this.tick();
//}, // No
//() => this.tick(), // This is the official way that works
//() => {this.tick();}, // This also works, but notice the difference between this and the last one
//() => {this.tick()}, // Also works
//() => {return this.tick();}, // Also works, think it equivalent as official one
1000
);
}
I think in a pure Javascript code snippet, all these ways are valid for setInterval to work. Especially, as the official document (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions) says, the arrow function definition is generally equivalent to the function () { ... } definition. But they just seem not equivalent in React and Babel.
After I examined the compiled Javascript code by Babel, they differ by a _this2 object.
// Javascript compiled by Babel
Clock.prototype.componentDidMount = function componentDidMount() {
var _this2 = this;
this.timerID = setInterval(
//this.tick // This is the compiled code using one of my way
/*function () {
this.tick();
},*/ // This is another one
function () {
return _this2.tick();
}, // This is the compiled code using the official way
//function () {
//_this2.tick();
//}, // This is the compiled code of another working example
1000
);
};
So, I am asking, why must we use the arrow function syntax here? Why the equivalent ways does not get compiled correctly? Is this a bug of React or Babel?
EDIT:
OK. Thank you very much, guys! That tricky thing about "this" in Javascript almost answers every question I have. Now I know there is a big difference between () => {} definition and function () {} definition, and why Babel doesn't do what I expect.
But, this does not answer why this could not work
this.timerID = setInterval(
this.tick, // This cannot work in CodePen
1000
);
Could someone also take a look on this "easy" line of code? I guess it's not related to "this" in the same way?
Function () {} and () =>{} are NOT the same thing.
Consider:
#1
function foo() {
console.log('foo');
}
vs:
#2
foo = () => {
console.log('foo')
}
Function #1 has a newly created context confined to the function, whereas function #2 doesn't create a new context this variable.
When Babel converts everything, I believe in order for function #2 to not have a new context it does something like this:
foo = () => {
console.log(this.data);
}
Becomes
var this_2 = this;
function foo () {
console.log(this_2.data);
}

Resources