Jest: Test for promise that rejects after timeout - javascript

I'm trying to write a test for the sad path of this function:
const awaitFirstStreamForPage = async page => {
try {
await page.waitForSelector('[data-stream="true"]', {
timeout: MAX_DELAY_UNTIL_FIRST_STREAM,
})
} catch (e) {
throw new Error(`no stream found for ${MAX_DELAY_UNTIL_FIRST_STREAM}ms`)
}
}
I managed to write a test that passes, but it takes 10 seconds to run because it actually waits for the test to finish.
describe('awaitFirstStreamForPage()', () => {
it('given a page and no active stream appearing: should throw', async () => {
jest.setTimeout(15000)
const browser = await puppeteer.launch({ headless: true })
const page = await getPage(browser)
let error
try {
await awaitFirstStreamForPage(page)
} catch (err) {
error = err
}
const actual = error.message
const expected = 'no stream found for 10000ms'
expect(actual).toEqual(expected)
await browser.close()
jest.setTimeout(5000)
})
})
There is probably a way to solve it using Jest's fake timers, but I couldn't get it to work. Here is my best attempt:
const flushPromises = () => new Promise(res => process.nextTick(res))
describe('awaitFirstStreamForPage()', () => {
it('given a page and no active stream appearing: should throw', async () => {
jest.useFakeTimers()
const browser = await puppeteer.launch({ headless: true })
const page = await getPage(browser)
let error
try {
awaitFirstStreamForPage(page)
jest.advanceTimersByTime(10000)
await flushPromises()
} catch (err) {
error = err
}
const actual = error.message
const expected = 'no stream found for 10000ms'
expect(actual).toEqual(expected)
await browser.close()
jest.useRealTimers()
})
})
which fails and throws with
(node:9697) UnhandledPromiseRejectionWarning: Error: no stream found for 10000ms
Even though I wrapped the failing function in a try/catch. How do you test a function like this using fake timers?

It's impossible to catch a rejection from awaitFirstStreamForPage(page) with try..catch if it's not awaited.
A rejection should be caught but after calling advanceTimersByTime and potentially after flushPromises.
It can be:
const promise = awaitFirstStreamForPage(page);
promise.catch(() => { /* suppress UnhandledPromiseRejectionWarning */ });
jest.advanceTimersByTime(10000)
await flushPromises();
await expect(promise).rejects.toThrow('no stream found for 10000ms');

The problem doesn’t seem to be the use of fake timers: the error you expected is the one being thrown. However, when testing functions that throw errors in Jest, you should wrap the error-throwing code in a function, like this:
expect(()=> {/* code that will throw error */}).toThrow()
More details here: https://jestjs.io/docs/en/expect#tothrowerror
Edit: For an async function, you should use rejects before toThrow; see this example: Can you write async tests that expect toThrow?

Related

How Can I Write a Test for a Javascript Asynchronous method like below usning jest

Please, I would appreciate if I can get a help out here to find a suitable test for the function below. It works perfectly fine in my project but I just cannot figure out why my test won't work. Thank you guys...
const GetScore = (() => {
const all = async () => {
const response = await fetch('https://us-central1-js-capstone-backend.cloudfunctions.net/api/games/ZUi2Xo2RRfSKd14twwPn/scores/', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
const data = await response.json();
return data;
};
return {
all,
};
})();
module.exports = GetScore;
My test is as follows
const GetScore = require('../modules/getScore');
let results = false;
it('returns an array of objects with all the scores', () => {
GetScore.all().then((response) => {
if (Array.isArray(response.result) === true) results = true;
expect(results).toBeTruthy();
});
});
The error I get is like this:
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "ReferenceError: fetch is not defined".] {
code: 'ERR_UNHANDLED_REJECTION'
Thank you guys in advance
Your test is not asynchronous so it is not waiting for the call to finish. Add an async/await in your test.
it('returns an array of objects with all the scores', async () => {
await GetScore.all().then((response) => {
if (Array.isArray(response.result) === true) results = true;
expect(results).toBeTruthy();
});
});
You should also write tests for the failures, so you might want to wrap the await in a try/catch.

Why am I keep getting Error: Timeout of 2000ms exceeded from Mocha on async?

I am trying to start javascript testing on Selenium, but I am stucked at the start.
MochaJS never waits for the end of test, instead throwing after 2 seconds
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/pavel/code/gscc/test/test.js)
My code is
const {Builder, By, Key, until} = require('selenium-webdriver');
let assert = require('chai').assert;
describe('Test Suite', function() {
it('should do', async function(done) {
try {
let driver = new Builder().forBrowser('firefox').build();
await driver.get('http://www.google.com/ncr');
const title = await driver.getTitle();
assert.equal(title, 'Google');
} catch(err) {
console.log(err);
} finally {
await driver.quit();
}
done();
})
})
Mocha says that I have unresolved promises in my code, but are there such?
Okay, after investigating I will try to answer my own question as I solved it.
Firstly, the Error: Timeout of 2000ms exceeded. is a common issue and it is just that - timeout was exceeded, but tests didn't run (cause they need more time to run). It is documented on stackoverflow pretty good.
The next problem is that Mocha awaits promise or done function to finish the test. When I wrote
it('should do', async function(done) {
try {
let driver = new Builder().forBrowser('firefox').build();
await driver.get('http://www.google.com/ncr');
const title = await driver.getTitle();
assert.equal(title, 'Google');
} catch(err) {
console.log(err);
} finally {
await driver.quit();
}
done();
})
It neither got the promise nor it got done, cause it does not work with async/await mechanism, only with standard promises.
So I removed done and also removed try-catch block completely, now it works at last!
The final code is
describe('Test Suite', function() {
this.timeout(0);
before(async function() {
this.driver = await new Builder().forBrowser('firefox').build();
});
it('should do', async function() {
await this.driver.get('http://www.google.com/ncr');
const title = await this.driver.getTitle();
assert.equal(title, 'Google1121312213');
})
after(async function() {
this.driver.quit();
})
});

Puppeteer Unhandled Rejected at: Promise - Why exception?

I am new to both Puppeteer and JavaScript. I am trying to automate some simple tasks, only that the elements appear within iframes - but I have resolved this. What I am unclear about is the exception thrown when I uncomment await browser.close().
My code:
const baseUrl = "https://test-environment.com/ABH2829.html?token=dhdj7s8383937hndkeie8j3jebd";
const puppeteer = require('puppeteer');
const expect = require('chai').expect;
const clickClothingButton = async () => {
try {
const browser = await puppeteer.launch({
headless: false,
slowMo: 250,
});
const page = await browser.newPage();
await page.setViewport({width: 1280, height: 800});
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled Rejected at: Promise', p, 'reason:', reason);
browser.close();
});
await page.goto(baseUrl, {waitUntil: 'networkidle2'});
const navigationPromise = page.waitForNavigation({timeout: 3000});
await page.waitForSelector('.widget-title');
const frame = page.frames().find(frame => frame.name() === 'iframe');
const clothingButton = await frame.$('#clothing-button');
clothingButton.click();
await navigationPromise;
await browser.close();
} catch (error) {
console.log(error);
throw new Error(error);
}
};
clickClothingButton();
Now this runs fine, but I always get the following:
Unhandled Rejected at: Promise Promise {
<rejected> Error: TimeoutError: Navigation Timeout Exceeded: 3000ms exceeded
If I try to just:
await browser.close();
Then it barfs with:
Unhandled Rejected at: Promise Promise {
<rejected> { Error: Protocol error (Runtime.callFunctionOn): Target closed.
What's the best way of handling this gracefully, and why can't I just close the browser? Bear in mind I'm still learning about Promises and the contracts that must be fulfilled for them.
First of all, the site you are appearing to access requires authentication.
You can use page.authenticate() to provide credentials for the HTTP authentication:
await page.authenticate({
username: 'username',
password: 'password',
});
Additionally, the timeout you set for page.waitForNavigation() is only 3000 ms (3 seconds), while the default is 30000 ms (30 seconds), so if it takes longer than the set amount of time to load the page, you are going to receive a TimeoutError.
I would strongly recommend allowing at least the default maximum navigation time of 30 seconds for the navigation to occur. You can use the timeout option in page.waitForNavigation() or use page.setDefaultNavigationTimeout().
Lastly, elementHandle.click() returns a Promise, so you need to await clothingButton.click():
await clothingButton.click();

Returning pending promises in async await

I am learning to use async / await and am having issues when trying to make an api request to set up my twilio device. Given the below code block, when I call device(), I get the following error message:
Uncaught
TwilioException {message: "Capability token is not valid or missing."}
message
:
"Capability token is not valid or missing."
__proto__
:
Object
I believe this is due to the fact that the json returned in the device function is still shown as pending. How do I resolve this and what am I doing wrong? Thanks.
Code block:
import {Device} from 'twilio-client';
const api = async () => {
try {
const response = await fetch('/api/twilio');
const json = await response.json();
if (response.status === 403) {
twilioConnectionFailure('Twilio has not been purchased.');
}
return json;
} catch (error) {
console.log(`Connection failed: ${error.message}`);
throw Error(error.message);
}
};
const device = () => {
const json = api();
Device.setup(json.token);
return Device;
};
export default device;
The api function is still asynchronous and returns a promise - you need to wait for it:
export default async function device() {
const json = await api();
// ^^^^^
Device.setup(json.token);
return Device;
}

Can you write async tests that expect toThrow?

I'm writing an async test that expects the async function to throw like this:
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(await getBadResults()).toThrow()
})
But jest is just failing instead of passing the test:
FAIL src/failing-test.spec.js
● expects to have failed
Failed: I should fail!
If I rewrite the test to looks like this:
expect(async () => {
await failingAsyncTest()
}).toThrow()
I get this error instead of a passing test:
expect(function).toThrow(undefined)
Expected the function to throw an error.
But it didn't throw anything.
You can test your async function like this:
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
'I should fail' string will match any part of the error thrown.
I'd like to just add on to this and say that the function you're testing must throw an actual Error object throw new Error(...). Jest does not seem to recognize if you just throw an expression like throw 'An error occurred!'.
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError("Some error message");
We must wrap the code in a function to catch the error. Here we are expecting the Error message thrown from someAsyncFunction should be equal to "Some error message". We can call the exception handler also
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError(new InvalidArgumentError("Some error message"));
Read more https://jestjs.io/docs/expect#tothrowerror
Custom Error Class
The use of rejects.toThrow will not work for you. Instead, you can combine the rejects method with the toBeInstanceOf matcher to match the custom error that has been thrown.
Example
it("should test async errors", async () => {
await expect(asyncFunctionWithCustomError()).rejects.toBeInstanceOf(
CustomError
)
})
To be able to make many tests conditions without having to resolve the promise every time, this will also work:
it('throws an error when it is not possible to create an user', async () => {
const throwingFunction = () => createUser(createUserPayload)
// This is what prevents the test to succeed when the promise is resolved and not rejected
expect.assertions(3)
await throwingFunction().catch(error => {
expect(error).toBeInstanceOf(Error)
expect(error.message).toMatch(new RegExp('Could not create user'))
expect(error).toMatchObject({
details: new RegExp('Invalid payload provided'),
})
})
})
I've been testing for Firebase cloud functions and this is what I came up with:
test("It should test async on failing cloud functions calls", async () => {
await expect(async ()=> {
await failingCloudFunction(params)
})
.rejects
.toThrow("Invalid type"); // This is the value for my specific error
});
This is built on top of lisandro's answer.
If you want to test that an async function does NOT throw:
it('async function does not throw', async () => {
await expect(hopefullyDoesntThrow()).resolves.not.toThrow();
});
The above test will pass regardless of the value returned, even if undefined.
Keep in mind that if an async function throws an Error, its really coming back as a Promise Rejection in Node, not an error (thats why if you don't have try/catch blocks you will get an UnhandledPromiseRejectionWarning, slightly different than an error). So, like others have said, that is why you use either:
.rejects and .resolves methods, or a
try/catch block within your tests.
Reference:
https://jestjs.io/docs/asynchronous#asyncawait
This worked for me
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(getBadResults()).reject.toMatch('foo')
// or in my case
expect(getBadResults()).reject.toMatchObject({ message: 'foo' })
})
You can do like below if you want to use the try/catch method inside the test case.
test("some test case name with success", async () => {
let response = null;
let failure = null;
// Before calling the method, make sure someAsyncFunction should be succeeded
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toEqual(SOME_MOCK_RESPONSE)
expect(error).toBeNull();
})
test("some test case name with failure", async () => {
let response = null;
let error = null;
// Before calling the method, make sure someAsyncFunction should throw some error by mocking in proper way
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toBeNull();
expect(error).toEqual(YOUR_MOCK_ERROR)
})
Edit:
As my given solution is not taking the advantage of inbuilt jest tests with the throwing feature, please do follow the other solution suggested by #Lisandro https://stackoverflow.com/a/47887098/8988448
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
test("It should test async on failing cloud functions calls", async () => {
failingCloudFunction(params).catch(e => {
expect(e.message).toBe('Invalid type')
})
});

Categories