React testing library - await findBy* vs promise.then - javascript

I have a test where I await for some text to be shown:
const allTodosPromise = findByText('All Todos')
My test actually checks if the HTMLElement returned has some style (to see if it is active or not):
expect(await allTodosPromise).toHaveStyle(activeItemStyle);
which fails with message "Unable to find an element with the text 'All Todos'...".
But, if I resolve my promise with a then the test passes, like:
allTodosPromise.then(htmlElement =>
expect(htmlElement).toHaveStyle(activeItemStyle)
);
Why? And yes, I did say that my test function is async.
Also trying to await in the first reference fails:
const allTodosElement = await findByText('All Todos');

You can try the waitFor function: https://testing-library.com/docs/dom-testing-library/api-async/#waitfor

Related

Not placing waitFor statement before findBy cause test to fail - React Testing Library

render(view)
let timerCellsList;
await waitFor(() => {
lockCellsList = screen.getAllByTestId('TimerCell');
expect(lockCellsList).toHaveLength(2);
});
const startTimerButton = within(timerCellsList[1]).getByRole('button');
userEvent.click(startTimerButton);
await waitFor(() => {}, {timeout: 0}); // the test passes with this line and fails without it.
// I can set the timeout to any number including 0.
const activeTimer = await screen.findByRole('cell', {name: /00:00/i});
expect(activeTimer).toBeInTheDocument();
I've written a test for which the user clicks a button. The button then makes a network request and on a 200 response it displays a timer which begins to count up in seconds. I'm using MSW to return mock responses to these requests. As the network request is clearly asynchronous, I'm searching for this timer by awaiting a call to screen.findByRole. The issue I'm having is that the test only seems to pass if I separate the call to userEvent.click(startTimerButton) and the call too await screen.findByRole('cell', {name: /00:00/i}) with a call too await waitFor(() => {}). It seems that this test will only pass if I let it sleep for any amount of time before searching for the timer. I don't understand why I wouldn't just be able to start searching for the timer immediately.
Without the waitFor statement, the error message I get back is:
Error: thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
var evt = document.createEvent('Event');
TypeError: Cannot read property 'createEvent' of null
Does anyone know what could be causing this? I would prefer to not have to hack around it as I'm doing now.
I have also tried changing my await findBy to a getBy wrapped inside a waitFor statement but that hasn't worked either. It seems I do just have to allow it to sleep for any amount of time before beginning the search
You are missing await in this line:
userEvent.click(startTimerButton);
So try:
await userEvent.click(startTimerButton);
the userEvent API is async since version 14.

Puppeteer's `page.evaluate` result differs from devTools console

I need to check for a certain service worker being registered. Unfortunately, page.evaluate returns undefined no matter what I do.
let page = await chrome.newPage();
await page.goto('http://127.0.0.1:8089/');
await page.waitFor(10000);
const isCorrectSW = await page.evaluate(async () => {
await navigator
.serviceWorker
.getRegistrations()
.then(registrations =>
registrations[0].active.scriptURL.endsWith('/target.js')
);
});
console.log(isCorrectSW);
isCorrectSW ends up being undefined, but if I enable devtools and run the same statement in the Chromium instance's devtools, I get the correct result. I can also observe the service worker attached in the browser's dev tools.
Is this a Puppeteer bug, or am I doing something incorrectly?
According to the documentation, page.evaluate returns undefined when the function passed returns a non-serializable value.
In your scenario, the function you are passing into page.evaulate does not return anything.
You are already using async, you can switch the function you are passing to be:
async () => {
const registrations = await navigator.serviceWorker.getRegistrations()
return registrations[0].active.scriptURL.endsWith('/target.js')
}

Jest spyOn call count after mock implementation throwing an error

I have a program that makes three post requests in this order
http://myservice/login
http://myservice/upload
http://myservice/logout
The code looks something like this
async main() {
try {
await this.login()
await this.upload()
} catch (e) {
throw e
} finally {
await this.logout()
}
}
where each method throws its own error on failure.
I'm using Jest to spy on the underlying request library (superagent). For one particular test I want to test that the logout post request is being made if the upload function throws an error.
I'm mocking the post request by throwing an exception.
const superagentStub = {
post: () => superagentStub
}
const postSpy = jest.spyOn(superagent, 'post')
.mockImplementationOnce(() => superagentStub)
.mockImplementationOnce(() => { throw new Error() })
.mockImplementationOnce(() => superagentStub)
const instance = new ExampleProgram();
expect(async () => await instance.main()).rejects.toThrow(); // This is fine
expect(postSpy).toHaveBeenNthCalledWith(3, 'http://myservice/logout')
If I don't mock the third implementation, the test will fail as logout() will throw its own error since the third post request will fail as a live call.
The spy in this case reports that only 1 call is made to the post method of the underlying library.
http://myservice/login
I find this strange because I am expecting 3 calls to the spy
http://myservice/login
http://myservice/upload -> but it throws an error
http://myservice/logout
Please keep in mind how to use expect(...).rejects.toThrow(). It's a bit tricky, though: https://jestjs.io/docs/expect#rejects
BTW: It's always nice to have ESLint active when coding with JavaScript. The following rule might then warn you about your error: https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/valid-expect.md ("Async assertions must be awaited or returned.")
Solution
You are missing an await in the beginning of the second-last line of your test-code. Replacing that line with the following should hopefully solve your problem:
await expect(() => instance.main()).rejects.toThrow();
which is the same as
await expect(async () => await instance.main()).rejects.toThrow();
You state // This is fine in your variant, but actually it isn't. You have a "false positive", there. Jest will most likely also accept the second-last line of your test even if you negate it, i.e. if you replace .rejects.toThrow() with .rejects.not.toThrow().
If you have more than one test in the same test-suite, Jest might instead state that some later test fails - even if it's actually the first test which causes problems.
Details
Without the new await in the beginning of the given line, the following happens:
expect(...).rejects.toThrow() initiates instance.main() - but doesn't wait for the created Promise to resolve or reject.
The beginning of instance.main() is run synchronously up to the first await, i.e. this.login() is called.
Mostly because your mockup to superagent.post() is synchronous, this.login() will return immediately. BTW: It might be a good idea to always replace async functions with an async mockup, e.g. using .mockResolvedValueOnce().
The Promise is still pending; JavaScript now runs the last line of your test-code and Jest states that your mockup was only used once (up to now).
The test is aborted because of that error.
The call to instance.main() will most likely continue afterwards, leading to the expected error inside instance.main(), a rejected Promise and three usages of your mockup - but all this after the test already failed.

testcafe requestLogger only logs the first test in the fixture

So I'm getting better with testcafe and one feature I'd like to learn is its RequestLogger.
So I've created an instance of it
import { RequestLogger } from 'testcafe';
const logger = RequestLogger(/some reg exp/, {
logRequestHeaders: true,
logRequestBody: true
});
export default logger;
and then tried to use it on a sample test fixture:
fixture `REQUEST LOGGER TEST`
.requestHooks(logger);
test('should contain 204 as a status code 1', async t => {
await t.useRole(role);
await model.doSmth(t);
await model.doSmthElse(t);
console.log(logger.requests);
await t
.expect(logger.requests[0].response.statusCode)
.eql(204);
await t
.expect(logger.requests[1].response.statusCode)
.eql(200);
});
While the first test works just fine, the second one, even if it's the same, will output an empty array once I'll try to console.log(logger.requests)
Any idea how to go about it?
I had the same problem because you have to wait for the Smart Assertion Query Mechanism of Testcafe before doing assertions on Logger.request array.
The documentation tells us that using count or contains predicates make Testcafe use the Smart Assertion Query mechanism
Suggestion from Marion may do the job if your request returns a 200 statusCode :
await t.expect(logger.contains(record => record.response.statusCode === 200)).ok();
I found simpler to do this instead, which does not depend on the returned http status
await t.expect(logger.count(() => true)).eql(1);
replace eql(1) with the number of requests you expect to be logged before doing your assertions

Selenium with async/await in JS, find and click on element

I'm trying to refactor my tests using Selenium webdriver and Mocha to ES7 with async/await functionality. I have got following piece of code:
await loginPage.loginAsAdmin()
/* THIS DOES NOT WORK */
//await layout.Elements.routePageButton().click()
/* THIS DOES WORK */
let a = await layout.Elements.routePageButton()
await a.click()
I don't understand why the particular does not work - I get:
TypeError: layout.Elements.routePageButton(...).click is not a function
Function before click method returns webElement, as you can see:
Layout:
routePageButton: async () => await findVisibleElement('#route_info a')
const findVisibleElement = utils.Methods.Element.findVisible
Method:
findVisible: async (cssSelector) => {
let elm = await driver().findElement(by.css(cssSelector))
return elm
}
The problem here is misunderstanding that await is a language keyword in ES2017 that allows you to block execution of the calling async function until a Promise returned by an invoked function resolves.
routePageButton() returns a Promise, and this is why the second syntax above works, as execution is blocked until the Promise resolves to a WebElement object.
However in the syntax you are using in the first example, the function that it is attempting to await on (click()) is never called, because a Promise does not have a click() function. Note that you have two awaits in your second syntax, but only one in your first.
To do what you are attempting to do in one line, you would have to do something like:
await (await layout.Elements.routePageButton()).click()

Categories