Jest - How to test that console.error is called? - javascript

I am trying to write a unit test with jest/enzyme that tests if console.error() has been called in the catch() of a try/catch, but trying to do so either results in a successful test when it should be unsuccessful, or an "Expected mock function to have been called, but it was not called" error.
Function to test:
export const playSound = (soundName, extension = 'wav') => {
try {
SoundPlayer.onFinishedPlaying(success => success);
SoundPlayer.playSoundFile(soundName, extension);
} catch (err) {
console.error(`Error playing sound '${soundName}':`, err);
return err;
}
};
So the above takes a single argument soundName, which is a string, and I'm trying to test that a console error is logged when no argument is passed in.
I've most-recently tried the below, which seems to be miles off, and wrongfully returns a passed test.
it('fails to play sound with no method arguments', async () => {
const consoleSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
try {
playSound();
expect(consoleSpy).not.toHaveBeenCalled();
} catch (err) {
expect(consoleSpy).toHaveBeenCalled();
}
});

Your playSound function will never throw, because you're swallowing the exception.
you simply need this:
it('fails to play sound with no method arguments', async () => {
const consoleSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
playSound();
expect(consoleSpy).toHaveBeenCalled();
});
you can also check return value of the function call, which will be exception object.
Also if you want to check if function throws you can use
expect(function() { playSound(); }).toThrow();
but this will fail, unless you don't catch the exception or rethrow.

Related

Axios instance promise.all error handling

I have the following code in my own async function that uses another imported function from module which is a custom wrap of axios inside try/catch block:
async function getCharacter (realmSlug, characterName) {
try {
const [{id, name, gender, faction, race, character_class, active_spec, realm, guild, level, last_login_timestamp, average_item_level, equipped_item_level}, {pets, unlocked_battle_pet_slots},{mounts}] = await Promise.all([
getCharacterSummary(realmSlug, characterName), -- custom axios instance
getCharacterPetsCollection(realmSlug, characterName),
getCharacterMountsCollection(realmSlug, characterName)
])
....
return result;
} catch (error) {
console.log(error.code);
if (error.response.status === 404 || error.response.status === 403) {
console.error(`${getCharacter.name},${characterName}#${realmSlug}`);
}
return { name: characterName, realm: realmSlug }
}
}
The problem is that if I use promise.all according to Stackoverflow 1,2 I can not handle errors. So the problem is when I call function to execute, my errors doesn't handle in (catch) block. At all. Even if I don't need print them, anyway I receive messages in console about 404 errors, but console.log(error.code) still gives me nothing. For example:
So is there any way to handle this annoying error messages in console somehow?
For example using .catch somewhere? Or using for await ... of or rxJS instead if it's possible?
Exporting function and using .catch
Even if I export this function getCharacter in another .js file and use the following code:
const getCharacter = require('./getCharacter');
let bulkCharacters = [{realmSlug, characterName},{realmSlug, characterName},... ,n] //array of characters for getCharacter request
const promises = bulkCharacters.map(async ({realmSlug, characterName}) => {
try {
return await getCharacter(realmSlug, characterName);
} catch (e) {
console.log(e)
}
});
let test = await Promise.all(promises)
.catch(function(arrayOfPromises, err) {
// log that I have an error, return the entire array;
console.log('A promise failed to resolve', err);
return arrayOfPromises;
})
.then(function(arrayOfPromises) {
console.log(arrayOfPromises)
})
;
console.log('stop')
I still receive errors in console, without triggering catch block inside getCharacter function or this file in which this function was imported and catch block is outside the function.

Asserting on catch block code inside forEach loop

I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});

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')
})
});

How to test the type of a thrown exception in Jest

I'm working with some code where I need to test the type of an exception thrown by a function (is it TypeError, ReferenceError, etc.?).
My current testing framework is AVA and I can test it as a second argument t.throws method, like here:
it('should throw Error with message \'UNKNOWN ERROR\' when no params were passed', (t) => {
const error = t.throws(() => {
throwError();
}, TypeError);
t.is(error.message, 'UNKNOWN ERROR');
});
I started rewriting my tests in Jest and couldn't find how to easily do that. Is it even possible?
In Jest you have to pass a function into expect(function).toThrow(<blank or type of error>).
Example:
test("Test description", () => {
const t = () => {
throw new TypeError();
};
expect(t).toThrow(TypeError);
});
Or if you also want to check for error message:
test("Test description", () => {
const t = () => {
throw new TypeError("UNKNOWN ERROR");
};
expect(t).toThrow(TypeError);
expect(t).toThrow("UNKNOWN ERROR");
});
If you need to test an existing function whether it throws with a set of arguments, you have to wrap it inside an anonymous function in expect().
Example:
test("Test description", () => {
expect(() => {http.get(yourUrl, yourCallbackFn)}).toThrow(TypeError);
});
It is a little bit weird, but it works and IMHO is good readable:
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
try {
throwError();
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e) {
expect(e.message).toBe("UNKNOWN ERROR");
}
});
The Catch block catches your exception, and then you can test on your raised Error. Strange expect(true).toBe(false); is needed to fail your test if the expected Error will be not thrown. Otherwise, this line is never reachable (Error should be raised before them).
#Kenny Body suggested a better solution which improve a code quality if you use expect.assertions():
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', () => {
expect.assertions(1);
try {
throwError();
} catch (e) {
expect(e.message).toBe("UNKNOWN ERROR");
}
});
See the original answer with more explanations: How to test the type of a thrown exception in Jest
EDIT 2022:
To use this approach and not trigger no-conditional-expect rule (if you're using eslint-plugin-jest), documentation of this rule suggest to use error wrapper:
class NoErrorThrownError extends Error {}
const getError = async <TError>(call: () => unknown): Promise<TError> => {
try {
await call();
throw new NoErrorThrownError();
} catch (error: unknown) {
return error as TError;
}
};
describe('when the http request fails', () => {
it('includes the status code in the error', async () => {
const error = await getError(async () => makeRequest(url));
// check that the returned error wasn't that no error was thrown
expect(error).not.toBeInstanceOf(NoErrorThrownError);
expect(error).toHaveProperty('statusCode', 404);
});
});
See: no-conditional-expect docs
I use a slightly more concise version:
expect(() => {
// Code block that should throw error
}).toThrow(TypeError) // Or .toThrow('expectedErrorMessage')
From my (albeit limited) exposure to Jest, I have found that expect().toThrow() is suitable if you want to only test an error is thrown of a specific type:
expect(() => functionUnderTest()).toThrow(TypeError);
Or an error is thrown with a specific message:
expect(() => functionUnderTest()).toThrow('Something bad happened!');
If you try to do both, you will get a false positive. For example, if your code throws RangeError('Something bad happened!'), this test will pass:
expect(() => functionUnderTest()).toThrow(new TypeError('Something bad happened!'));
The answer by bodolsog which suggests using a try/catch is close, but rather than expecting true to be false to ensure the expect assertions in the catch are hit, you can instead use expect.assertions(2) at the start of your test where 2 is the number of expected assertions. I feel this more accurately describes the intention of the test.
A full example of testing the type and message of an error:
describe('functionUnderTest', () => {
it('should throw a specific type of error.', () => {
expect.assertions(2);
try {
functionUnderTest();
} catch (error) {
expect(error).toBeInstanceOf(TypeError);
expect(error).toHaveProperty('message', 'Something bad happened!');
}
});
});
If functionUnderTest() does not throw an error, the assertions will be be hit, but the expect.assertions(2) will fail and the test will fail.
I manage to combine some answers and end up with this:
it('should throw', async () => {
await expect(service.methodName('some#email.com', 'unknown')).rejects.toThrow(
HttpException,
);
});
Modern Jest allows you to make more checks on a rejected value. For example, you could test status code of http exception:
const request = Promise.reject({statusCode: 404})
await expect(request).rejects.toMatchObject({ statusCode: 500 });
will fail with error
Error: expect(received).rejects.toMatchObject(expected)
- Expected
+ Received
Object {
- "statusCode": 500,
+ "statusCode": 404,
}
Further to Peter Danis' post, I just wanted to emphasize the part of his solution involving "[passing] a function into expect(function).toThrow(blank or type of error)".
In Jest, when you test for a case where an error should be thrown, within your expect() wrapping of the function under testing, you need to provide one additional arrow function wrapping layer in order for it to work. I.e.
Wrong (but most people's logical approach):
expect(functionUnderTesting();).toThrow(ErrorTypeOrErrorMessage);
Right:
expect(() => { functionUnderTesting(); }).toThrow(ErrorTypeOrErrorMessage);
It's very strange, but it should make the testing run successfully.
In case you are working with Promises:
await expect(Promise.reject(new HttpException('Error message', 402)))
.rejects.toThrowError(HttpException);
You must wrap the code of the function that you are expecting in another arrow function, otherwise the error will not be caught and the assertion will fail.
the function you want to test :
const testThrowingError = () => {
throw new Error();
};
the test:
describe("error function should Throw Error", () => {
expect(() =>testThrowingError()).toThrowError();
});
resource:
https://jestjs.io/docs/expect#tothrowerror
I haven't tried it myself, but I would suggest using Jest's toThrow assertion. So I guess your example would look something like this:
it('should throw Error with message \'UNKNOWN ERROR\' when no parameters were passed', (t) => {
const error = t.throws(() => {
throwError();
}, TypeError);
expect(t).toThrowError('UNKNOWN ERROR');
//or
expect(t).toThrowError(TypeError);
});
Again, I haven't test it, but I think it should work.
Check out toThrow method.
You must wrap the code in an additional function callback!
You should check both: the error message and its type.
For example:
expect(
() => { // additional function wrap
yourCodeToTest();
}
).toThrow(
new RangeError('duplicate prevArray value: A')
);
Because of additional callback wrap, the code will not be run immediately, so jest will be able to catch it.
You should always check the error message to be sure you are checking the correct throw case and not getting another error your code may throw.
It is also nice to check the error type, so the client code may rely on it.
Jest has a method, toThrow(error), to test that a function throws when it is called.
So, in your case you should call it so:
expect(t).toThrowError(TypeError);
The documentation.
I have successfully used this
await expect(
async () => await apiCalls()
).rejects.toThrow();
There's a way to wait an error that comes from a async function, you just have to write your code like in the example bellow
await expect(yourAsyncFunction()).rejects.toThrowError();
The documentation is clear on how to do this. Let's say I have a function that takes two parameters and it will throw an error if one of them is null.
function concatStr(str1, str2) {
const isStr1 = str1 === null
const isStr2 = str2 === null
if(isStr1 || isStr2) {
throw "Parameters can't be null"
}
... // Continue your code
Your test
describe("errors", () => {
it("should error if any is null", () => {
// Notice that the expect has a function that returns the function under test
expect(() => concatStr(null, "test")).toThrow()
})
})
I ended up writing a convenience method for our test-utils library
/**
* Utility method to test for a specific error class and message in Jest
* #param {fn, expectedErrorClass, expectedErrorMessage }
* #example failTest({
fn: () => {
return new MyObject({
param: 'stuff'
})
},
expectedErrorClass: MyError,
expectedErrorMessage: 'stuff not yet implemented'
})
*/
failTest: ({ fn, expectedErrorClass, expectedErrorMessage }) => {
try {
fn()
expect(true).toBeFalsy()
} catch (err) {
let isExpectedErr = err instanceof expectedErrorClass
expect(isExpectedErr).toBeTruthy()
expect(err.message).toBe(expectedErrorMessage)
}
}
A good way is to create custom error classes and mock them. Then you can assert whatever you want.
MessedUpError.ts
type SomeCrazyErrorObject = {
[key: string]: unknown,
}
class MessedUpError extends Error {
private customErrorData: SomeCrazyErrorObject = {};
constructor(err?: string, data?: SomeCrazyErrorObject) {
super(err || 'You messed up');
Object.entries(data ?? {}).forEach(([Key, value]) => {
this.customErrorData[Key] = value;
});
Error.captureStackTrace(this, this.constructor);
}
logMe() {
console.log(this.customErrorData);
}
}
export default MessedUpError;
messedUpError.test.ts
import MessedUpError from './MessedUpError';
jest.mock('./MessedUpError', () => jest.fn().mockImplementation((...args: any[]) => ({
constructor: args,
log: () => {},
})));
type MessedUpErrorContructorParams = Expand<typeof MessedUpError['prototype']>
const MessedUpErrorMock = MessedUpError as unknown as jest.Mock<MessedUpError, [MessedUpErrorContructorParams]>;
const serverErrorContructorCall = (i = 0) => ({
message: MessedUpErrorMock.mock.calls[i][0],
...MessedUpErrorMock.mock.calls[i][1] || {},
});
beforeEach(() => {
MessedUpErrorMock.mockClear();
});
test('Should throw', async () => {
try {
await someFunctionThatShouldThrowMessedUpError();
} catch {} finally {
expect(MessedUpErrorMock).toHaveBeenCalledTimes(1);
const constructorParams = serverErrorContructorCall();
expect(constructorParams).toHaveProperty('message', 'You messed up');
expect(constructorParams).toHaveProperty('customErrorProperty', 'someValue');
}
});
The assertions always go inside the finally clause. This way it will always be asserted. Even if the test does not throw any errors.
There is also an easier way to assert against the error message. The beauty of this method is that you don't need to reconstruct the error object or have the full error message. As long as your error contains part of the error message we can assume it is of the correct type. i.e
const printOnlyString = (str) => {
if(typeof str !== "string"){
throw Error("I can only print strings ${typeof str) given");
}
else {
console.log(str);
}
}
expect(() => printOnlyString(123)).toThrow(/can only print strings/)
Try:
expect(t).rejects.toThrow()

Test error handling of async function with jasmine

I have the following async function:
async $onInit() {
try {
this.settings = await this.Service.getSettings(this.companyId);
} catch (error) {
this.uiFeedback.showSystemError('There was an error processing the request.', error);
}
}
I am trying to test if the function displays an error if the async function catches an error. At the moment the only spec which successfully managed to do this was the following:
it('should show error if api call to get availability settings fails', () => {
Company.getSettings = () => {
throw new Error();
};
try {
cmp.$onInit().catch(() => {});
}
catch (error) {
expect(uiFeedback.showSystemError).toHaveBeenCalled();
}
});
So basically i need to add two extra catch blocks within my test to get this test to work. Otherwise i get the following error:
ERROR: 'Unhandled promise rejection', 'error'
Is there a better way to do this?
So it turns out the issue is simply how karma handles errors for async functions. this.uiFeedback was undefined in the $onInit function. However karma did not show the uiFeedback is undefined error until i ran the tests in Chrome and checked the console there. Something for everyone to watch out for when testing async functions with karma jasmine.

Categories