Compare errors in Chai - javascript

I needed to implement my own error class in ES6 (with node v4):
class QueryObjectError {
constructor (message) {
this.message = message;
}
}
I have a portion of code that throws said error type:
function myFunct () {
throw new QueryObjectError('a message');
}
And I am using Mocha and Chai to test the the function throws the expected error with the expected message:
it('is a test', function (done) {
var err = new QueryObjectError('abc');
assert.throw(myFunct, err);
done();
});
The test passes although the QueryObjectError objects have different messages and I want to test the case in which deep equality is checked. Any way to solve this with the given tools?

There are two salient issues with your code:
You do not use assert.throw correctly. You should pass the constructor to the expected exception as the 2nd argument and a regular expression or a string as the 3rd argument. If the 3rd argument is a string, Chai will check that the string exist in the exception's message. If it is a regular expression, it will test whether the message is matched by the expression.
Your exception should have a toString method that returns the message, otherwise Chai won't know how to check the message.
Here is an example showing a failure and a success:
import { assert } from "chai";
class QueryObjectError {
constructor (message) {
this.message = message;
}
toString() {
return this.message;
}
}
function myFunct () {
throw new QueryObjectError('a message');
}
it('is a test', function () {
assert.throw(myFunct, QueryObjectError, 'abc');
});
it('is another test', function () {
assert.throw(myFunct, QueryObjectError, /^a message$/);
});

Related

Jasmine test case error: spy error to have been called

I have the following code that needs to be tested in Jasmine
sampleMethod() {
if (this.sampleObject) {
// business logic
} else {
console.error('sampleObject not initialized');
}
}
I had referred to Spying on console.error() with Jasmine to add jasmine test to monitor console.error.
it('should check for console.error', () => {
spyOn(console, 'error');
component.sampleMethod();
expect(console.error).toHaveBeenCalled();
});
However, I'm getting the following error:
Error: Expected spy error to have been called.
SampleObject is undefined. I've also tried adding spyOn to beforeEach. Still, I'm getting the error.
Can you try this to ensure that sampleObject is undefined and try assigning the spy to a variable?
it('should check for console.error', () => {
const errorSpy = spyOn(console, 'error');
component.sampleObject = undefined;
component.sampleMethod();
expect(errorSpy).toHaveBeenCalled();
});

Mocking different log levels on Azure Function context with jest in javascript

I have been playing around with testing my Azure Functions, but I am unable to mock the context log function.
For example I have the following Azure Function:
module.exports = async function (context, req) {
if (req.query.isGood) {
context.log("Goooood!!!")
context.res = {
body: {
message: "This is good!"
}
};
} else {
context.log.error("Not gooood!!!")
context.res = {
status: 404,
body: {
message: "This is not good!"
}
};
}
}
So I want to check the amount of times a certain log occured, for example 'log.error' occured once and 'log' occured twice, but I am unable to mock this.
I tried a couple of combinations like:
log: {
"": jest.fn(),
"error": jest.fn()
}
At this point I'm clueless on how to mock these functions, and am wondering if it is even possible? And how do you create these kind of functions?
In order to do this you need to create a closure function that is immediately invoked. Inside that function, create your default and then add to it the additional methods. In Typescript, you need to cast jest.fn() to the any type to get around type checking.
log: (function() {
let main = <any>jest.fn((message) => message) ;
let info = jest.fn((message) => message);
main.info = info;
return main;
})()
Once you are back in your test, this should then behave as expected:
test ('log test', () => {
context.log("foo");
context.log.info("bar");
expect(context.log.mock.calls[0][0]).toEqual("foo");
expect(context.log.info.mock.calls[0][0]).toEqual("bar");
});

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

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.

How can I check the type of the error thrown asyncronously in Jest?

Suppose I have a function like this:
const f = async () => {
throw new Error('Huh???!');
};
And I want to test that it throws a RangeError (not just an Error) using Jest.
test('f throws a RangeError', () => {
expect(f()).rejects.toThrowError(RangeError);
});
However this test passes.
How can I check the type of the error thrown asyncronously in Jest?
You can use the toBeInstanceOf matcher after rejects:
test('f throws a RangeError', () => {
expect(f()).rejects.toBeInstanceOf(RangeError);
});

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()

Categories