I'm having a create-react-app and am trying to test a functional component. The component makes a service call on initial mount and sets the state and populates a material ui select based on the results.
Now, the user can pick one of those options on the select and I make another service call to retrieve more data based on that.
I am able to mock the initial service call, return mock values and prove that the select got populated but I am having trouble with finding the mocked option text in the select. I get an error - Unable to find an element with the text. This could be because the text is broken up by multiple elements.
Here is how my test looks like:
const fetchMock = jest.spyOn(window, 'fetch').mockImplementation(() =>
Promise.resolve({ ok: true, json: () => Promise.resolve([{itemName:
'Color', itemId: 23},
{itemName: 'Car', itemId: 34}])}));
const localhostURL = "http://localhost:8080";
let retrieveByLabelTextFunction;
let retrieveByRole;
act(() => { const { getByLabelText } =
render(<ItemSelection/>);
retrieveByLabelTextFunction = getByLabelText;
retrieveByRole = getByRole;
});
expect(fetchMock).toBeCalledWith(localhostURL + '/items/');
findByDisplayValue("Color");
findByDisplayValue("Car");
const items = retrieveByLabelTextFunction("Items");
fireEvent.mouseDown(items);
const itemListbox = within(retrieveByRole("listbox"));
fireEvent.click(itemListbox.getByText("I34 - Car"));
Any thoughts on how to test this scenario? I34 in the text is added by the code under test appending the id basically.
I'm using react-virtualized for a lengthy (1000+) list of items to select from. And I'm trying to set up an end to end test that requires clicking on one of the elements that are not currently rendered.
Ordinarily, I'd simply use something like:
await t.click(
ReactSelector('ListPage')
.findReact('ListItem')
.nth(873) // or .withText(...) or .withProps(...)
)
But because only a small subset of the ListItems are rendered, TestCafe fails to find the desired element.
I've been trying to figure out how to use TestCafe's ClientFunction to scroll the list container so the desired ListItem is rendered.
However, I'm running into a few issues:
Is there a way to share a Selector into the ClientFunction and modify the DOM element's scrollTop? Or do I have to re-query the element via the DOM directly?
Due to the ListItems being different heights, the scroll position is not a simple calculation of index x item height. How can I keep updating/scrolling within this function until the desired Selector is visible?
Is there a way to share a Selector into the ClientFunction and modify the DOM element's scrollTop?
There is a way to put Selector into the Client Function. Please refer to this example in the TestCafe documentation.
How can I keep updating/scrolling within this function until the desired Selector is visible?
You can use the TestCafe exist property to check if the element is rendered or not. The following example demonstrates the approach:
import { Selector } from 'testcafe';
fixture`Getting Started`.page('https://bvaughn.github.io/react-virtualized/#/components/List')
test('Test 1', async t => {
const dynamicRowHeightsInput = Selector('._1oXCrgdVudv-QMFo7eQCLb');
const listItem = Selector('._113CIjCFcgg_BK6pEtLzCZ');
const targetItem = listItem.withExactText('Tisha Wurster');
await t.click(dynamicRowHeightsInput);
while (!await targetItem.exists) {
const currentLastRenderdItemIndex = await listItem.count -1;
const currentLastRenderdItemText = await listItem.nth(currentLastRenderdItemIndex).textContent;
const currentLastRenderdItem = await listItem.withExactText(currentLastRenderdItemText);
await t.hover(currentLastRenderdItem);
}
await t
.hover(targetItem)
.click(targetItem);
await t.debug();
});
To scroll the list container I used the hover action with a last rendered listItem as a target element.
UPDATED 3-26-2019
I am trying to give users the ability to add events to the Calendar and for those events to appear upon input. I am using a React.js front end and can get the events to render in a new calendar but it does not delete the old one (I just end up with 10+ calenders that render on top of each other).
I've tried using the .destroy() method and then re-rendering the calendar but the method doesn't seem to be available/functioning so I figured I would try rerenderEvents() and keep the same calendar object but that function doesn't seem to be accessible either. Would love some assistance in solving this from anyone who is familiar with FullCalendar v.4
Once the user inputs the data it's collected into a data object and append it into the state but if I just mirror the above, it renders a whole new calendar on top of the old one.
It's like I cannot get the Calendar object once rendered to make those method calls, but am having trouble capturing it in a separate function. All the Docs say is call the .destroy() or call the .rerenderEvents() and not what to call them on.
//This successfully Renders
componentOnMount() {
var calendarNew = document.getElementById('calendar');
let newCalendar = new Calendar(calendarNew, {
plugins: [ dayGridPlugin, timeGridPlugin, listPlugin ],
events: this.state.events
});
console.log(newCalendar)
await this.setState({ calendarObj: newCalendar })
// let myNewEvents = newCalendar.getEvents()
// console.log(myNewEvents)
// let stateEvents = this.state.calendarObj.getEvents()
// console.log(stateEvents)
this.state.calendarObj.render();
}
I've tried the following to mitigate the double render...
async handleNewEvent() {
// code that creates an object and sets the state to the new event array//
var newEventArr = existingEvents.concat(newEvent)
console.log(newEventArr)
await this.setState({ events: newEventArr })
await this.setState({ currentIndex: (this.state.currentIndex + 1) })
this.props.change(this.state.events, this.state.currentIndex)
//Contains the new event array in the console
console.log(this.state.events)
this.state.calendarObj.rerenderEvents();
}
But this does nothing. I cannot even render() from the handleNewEvent() as it appears as if the state is unable to hold functions in it and I cannot find a way to pass the function from the ComponentDidMount() to the handleNewEvent(). Im at a loss...
I'm using Puppeteer to do e2e tests for an app built with React and Material-UI.
In my login form I'm trying to click on the login button but I get loginBtn.click is not a function - that's because Material UI (version 0.21.0) is wrapping the <RaisedButton> with an extra div so I can only reach the outer element. I need a way to access the inner element and then simulate the click event.
I've tried this:
const loginBtn = await page.$eval(
'.login-form-button',
(element) => element.innerHTML
);
await loginBtn.click();
But it seems like element.innerHTML is just a text and not clickable.
Any ideas how can I get the inner element?
page.$eval selects an element (.login-form-button) and passes that element to the second argument ((element) => element.innerHTML), which will in your case return the HTML of the node as a string. Therefore loginBtn is not a button but a string. To click an element you might want to use page.click.
If you have a more complex term to execute I recommend using page.evaluate. In your case you could use it like this to query the element and click its first child element:
await page.evaluate(() => {
document.querySelector('.login-form-button').firstElementChild.click();
});
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);
});