asserting against thrown error objects in jest - javascript

I have a function which throws an object, how can I assert that the correct object is thrown in jest?
it('should throw', () => {
const errorObj = {
myError: {
name: 'myError',
desc: 'myDescription'
}
};
const fn = () => {
throw errorObj;
}
expect(() => fn()).toThrowError(errorObj);
});
https://repl.it/repls/FrayedViolentBoa

If you are looking to test the contents of a custom error (which I think is what you are trying to do). You could catch the error then perform an assertion afterwards.
it('should throw', () => {
let thrownError;
try {
fn();
}
catch(error) {
thrownError = error;
}
expect(thrownError).toEqual(expectedErrorObj);
});
As Dez has suggested the toThrowError function will not work if you do not throw an instance of a javascript Error object. However, you could create your custom error by decorating an instance of an error object.
e.g.
let myError = new Error('some message');
myError.data = { name: 'myError',
desc: 'myDescription' };
throw myError;
Then once you had caught the error in your test you could test the custom contents of the error.
expect(thrownError.data).toEqual({ name: 'myError',
desc: 'myDescription' });

You need to throw a Javascript Error object, so the Jest toThrowError method identifies that an error has been thrown. Also the toThrowError looks to match the message of the error thrown or if an error has been thrown if you just check by .toThrowError().
it('should throw', () => {
const errorObj = {
myError: {
name: 'myError',
desc: 'myDescription'
}
};
const fn = () => {
throw new Error(errorObj.myError.desc);
}
expect(() => fn()).toThrowError("myDescription");
});
If you want to check the whole object is being passed as it is, you need to check it like this:
it('should throw', () => {
const errorObj = {
myError: {
name: 'myError',
desc: 'myDescription'
}
};
const fn = () => {
throw errorObj;
}
expect(() => fn()).toThrowError(new Error(errorObj));
});

It's known issue in jest, see https://github.com/facebook/jest/issues/8140
Meanwhile, here is my workaround - https://github.com/DanielHreben/jest-matcher-specific-error

If the objective is to check partial content of error, we can use Jest expect.objectContaining to help us keep code simple and check object payload returned as error :
const invalidJob = () => {
throw {
type: '/errors/invalid-job',
message: 'This job is invalid',
};
};
expect(() => invalidJob()).toThrowError(
expect.objectContaining({
type: '/errors/invalid-job',
})
);
Also possible with nested objects :
const invalidJob = () => {
throw {
response: {
type: '/errors/invalid-job',
message: 'This job is invalid',
},
status: 400
};
};
expect(() => invalidJob()).toThrowError(
expect.objectContaining({
status: 400,
response: expect.objectContaining({
type: '/errors/invalid-job'
})
})
);

You could add a custom matcher.
1. Custom Matcher
import { CsrfError } from '../src/shield';
declare global {
namespace jest {
interface Matchers<R> {
toThrowCsrfError(expected: {
statusCode: number;
message: string;
}): CustomMatcherResult;
}
}
}
const mismatchResult = (message: string) => ({
pass: false,
message: () => message,
});
expect.extend({
toThrowCsrfError(received, expected): jest.CustomMatcherResult {
try {
received();
} catch (error) {
const isCsrfError = error instanceof CsrfError;
if (!isCsrfError) {
return mismatchResult('Not an CsrfError Error');
}
if (error.message !== expected.message) {
return mismatchResult(
`Recieved Message "${error.message}" different from expected "${expected.message}"`
);
}
if (error.statusCode !== expected.statusCode) {
return mismatchResult(
`Recieved statusCode "${error.statusCode}" different from expected "${expected.statusCode}"`
);
}
return {
pass: true,
message: () => ``,
};
}
return {
pass: false,
message: () => `Expected to throw, but didn't`,
};
},
});
2. Add to setupFilesAfterEnv
In your jest.config add the file above to your setupFilesAfterEnv list, for example:
const config = {
setupFilesAfterEnv: ['./test/matchers.ts'],
};
module.exports = config;
3. Call
expect(() => {
shield({ pathname: 'https://example.com/' });
}).toThrowCsrfError({ statusCode: 401, message: 'No CSRF cookie.' });

When I need to test a custom error (subclass of Error), I use the following approach:
export class CustomError extends Error {
constructor(public code: string, public data: any) {
super(`Custom Error`);
}
}
export async function method(): Promise<void> {
throw new CustomError('ABC001', { field: 'X' });
}
// test:
it('should throw a CustomError for field X', () => {
expect.assertions(1); // Expects for an error
return method().catch(e => {
expect(Object.assign({}, e)).toStrictEqual({
code: 'ABC001',
data: { field: 'X' }
});
});
});

Related

How to assert an error being thrown by mocked method using Jest in AWS Lambda

I have an AWS Lambda function called getTables that runs when you hit an API Gateway endpoint.
I would like to mock an exception being thrown by a section of my code that uses de AWS Glue SDK.
How can I properly mock an exception being thrown by a method and how should I assert it?
The test that I have doesn't seem to be working cause I get an unexpected exception which indicates that another part of the code is trying to use the response of the mocked code (meaning it didn't throw?):
it('should throw on db not found', async () => {
mockGetTables.mockReturnValue(new Error("EntityNotFoundException"));
await getTables(MOCK_REQUEST_EVENT_FAIL, mockContext, mockCallback);
expect(mockCallback).rejects.toEqual('EntityNotFoundException');
});
Here's my Lambda code:
export const getTables = async (
event: APIGatewayProxyEvent,
_context: Context,
callback: Callback<APIGatewayProxyResult>
) => {
console.log('Executing /getTables Activity.');
console.log(`/getTables event: ${JSON.stringify(event)}.`);
try {
const { queryStringParameters } = event;
let catalogIdParam: string = null;
let databaseNameParam: string = null;
let maxResultsParam: number = null;
let nextTokenParam: string = null;
if (queryStringParameters) {
const { catalogId, databaseName, maxResults, nextToken } = queryStringParameters;
catalogIdParam = catalogId || null;
databaseNameParam = databaseName || null;
maxResultsParam = maxResults ? parseInt(maxResults) : null;
nextTokenParam = nextToken || null;
}
const glueClientInstance: GlueClient = GlueClient.getInstance();
//I'd like to mock the following async method and throw "EntityNotFoundException"
//just like API Gateway (or lambda) would do.
const { TableList, NextToken }: GetTablesResponse = await glueClientInstance.getTables(
databaseNameParam,
catalogIdParam,
maxResultsParam,
nextTokenParam
);
const pandaUITableList: PandaUIGlueTable[] = convertToPandaUITableList(TableList);
callback(null, {
statusCode: 200,
body: JSON.stringify({
TableList: pandaUITableList,
NextToken,
}),
});
} catch (error) {
console.log(`An error ocurred while executing /getTables activity: ${JSON.stringify(error)}`);
if (error.code === 'EntityNotFoundException') {
callback(null, {
statusCode: 400,
body: JSON.stringify({
error: error.message,
}),
});
}
//Generic/CatchAll handler that I'll test later once I figure this one out
handlerApiError(error, callback);
}
};
For reference, this is the atual error I'm trying to mock and throw:
{
"message": "Database fdv_lin not found.",
"code": "EntityNotFoundException",
"time": "2022-03-29T00:47:07.475Z",
"requestId": "fff5d84c-59de-441d-a204-e08ede830931",
"statusCode": 400,
"retryable": false,
"retryDelay": 76.52610613917457
}
You can use jest's toHaveBeenCalledWith() or toHaveBeenLastCalledWith() to assert that the callback is called with the appropriate payload in the catch clause. In this case, it is the below example.
callback(null, {
statusCode: 400,
body: JSON.stringify({
error: error.message,
}),
});
Possible Solution
const mockCallback:Callback<APIGatewayProxyResult> = jest.fn();
it('should throw on db not found', async () => {
mockGetTables.mockReturnValue(new Error('EntityNotFoundException'));
await getTables(MOCK_REQUEST_EVENT_FAIL, mockContext, mockCallback);
expect(mockCallback).toHaveBeenCalledWith(null, {
statusCode: 400,
body: JSON.stringify({
error: {
message: 'Database fdv_lin not found.',
code: 'EntityNotFoundException',
time: '2022-03-29T00:47:07.475Z',
requestId: 'fff5d84c-59de-441d-a204-e08ede830931',
statusCode: 400,
retryable: false,
retryDelay: 76.52610613917457,
},
}),
});

Test functions cannot both take a 'done' callback

I'm trying to create a simple test with nestjs, and I'm getting this error
Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.
Returned value: Promise {}
The unit test is so simple, but I get an error when I use done();
it('throws an error if a user signs up with an email that is in use', async (done) => {
fakeUsersService.find = () => Promise.resolve([{ id: 1, email: 'a', password: '1' } as User]);
try {
await service.signup('asdf#asdf.com', 'asdf');
} catch (err) {
done();
}
});
You are combining Async/Await and Done.
Either use asnyc/await, or done.
it('throws an error if user signs up with email that is in use', async () => {
try {
await service();
expect(...);
} catch (err) {
}
});
or use the done format
it('throws an error if user signs up with email that is in use', (done) => {
...
service()
.then( ...) {}
.catch( ...) {}
}
done();
});
for the last version from jest, you can't use `async/await , promise and done together.
the solution is
it("throws an error if user sings up with email that is in use", async () => {
fakeUsersService.find = () =>
Promise.resolve([{ id: 1, email: "a", password: "1" } as User]);
await expect(service.signup("asdf#asdf.com", "asdf")).rejects.toThrow(
BadRequestException
);
});
change BadRequestException according to your listening exception
Before v27, jest use jest-jasmine2 by default.
For version 27, jest uses jest-circus which doesn’t support done callback.
So you need to change the default testRunner.
Override with react-app-rewired worked for me
// config-overrides.js
module.exports.jest = (config) => {
config.testRunner = 'jest-jasmine2';
return config;
};
For the last version from jest, you can't use `async/await , promise and done together (Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise.).
the solution is
user.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
AfterInsert,
AfterRemove,
AfterUpdate,
} from 'typeorm';
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
email: string;
#Column()
password: string;
#AfterInsert()
logInsert() {
console.log('Inserted User with id', this.id);
}
#AfterUpdate()
logUpdate() {
console.log('Updated User with id', this.id);
}
#AfterRemove()
logRemove() {
console.log('Removed User with id', this.id);
}
}
auth.service.spec.ts
it('throws an error if user signs up with email that is in use', async () => {
fakeUsersService.find = () =>
Promise.resolve([{ id: 1, email: 'typescript#nestjs.jestjs', password: '1' } as User]);
expect(async () => {
const email = 'asdf#asdf.com';
const password = 'asdf';
await service.signup(email, password);
}).rejects.toThrow(BadRequestException);
});
Also, if you want to use both you can downgrade your current version of jest to : 26.6.3.
Worked fine for me, I'm using async + done
it('throws an error if a user signs up with an email that is in use', async () => {
await service.signup('asdf#asdf.com', 'asdf');
try {
await service.signup('asdf#asdf.com', 'asdf');
} catch (e) {
expect(e.toString()).toMatch('email in use');
}
});
in order for it to work, you can do the following:
it('throws an error if a user signs up with an email that is in use', async () => {
fakeUsersService.find = () =>
Promise.resolve([
{ id: 1, email: 'test#test.com', password: 'somePassword' } as User,
]);
expect(async () => {
await service.signup('test#test.com', 'somePassword')
}).rejects.toThrow(BadRequestException)
});
You can use this hack for some cases =)
it('should make an api request', (done) => {
const asyncCall = async () => {
await callbackWithApiInside();
setTimeout(() => {
expect(api).toHaveBeenNthCalledWith(1, payload);
done();
}, 1000);
};
asyncCall();
});

Jest mock not always works in async test

I have a function and I want to test it using Jest.
function handleRegister() {
return (req, res) => {
try {
const credentials = {
login: req.body.email,
password: req.body.password
}
res.status(201).send({ msg: 'User registration achieved successfully' }) //LINE 10
res.status(201).send({ msg: 'User registration achieved successfully' }) //LINE 11
auth.register(credentials, (err, result) => {
console.log('register', auth.getUsers())
if (result.status === 201) {
res.status(201).send({ msg: 'User registration achieved successfully' }) //LINE 17
console.log('User registration achieved successfully')
}
})
} catch(err) {
}
}}
My test code is:
test('should return status 201 and msg', done => {
try {
const fun = handlers.handleRegister()
const res = {
status: jest.fn().mockReturnThis(),
send: function () {
done()
}
}
fun({ body: { email: 'a', password: 'a' } }, res)
expect(res.status).toBeCalledWith(201)
} catch(err) {
done(err)
}
})
The problem is that function handlerRegister line 10 and 11 is correctly executed, but at line 17 I got an error:
/home/anna/Desktop/dev/exampleShop/backend/handlers.js:149
res.status(201).send({
^
TypeError: Cannot read property 'send' of undefined
at auth.register (/home/anna/Desktop/dev/exampleShop/backend/handlers.js:149:26)
at addAccountToDB (/home/anna/Desktop/dev/exampleShop/backend/auth.js:69:7)
at addAccountToDB (/home/anna/Desktop/dev/exampleShop/backend/auth.js:81:3)
at hashPassword (/home/anna/Desktop/dev/exampleShop/backend/auth.js:68:5)
at AsyncWrap.crypto.scrypt (/home/anna/Desktop/dev/exampleShop/backend/auth.js:87:5)
at AsyncWrap.wrap.ondone (internal/crypto/scrypt.js:43:48)
If I use js, not a mock in property res, like:
const res = {
status: function(){return this},
send: function () {
done()
}
}
}
then I don't have this error.
Can someone explain me what is wrong?
There is a scoping issue. res is not defined where you are calling res.send(), because res is being defined inside of the try block.
Either move your expect statement inside of the try like below, or define res in the same scope as your expect statement.
Also you can't call .toBeCalledWith on a function that is not a mocked function. So notice that I have defined res.send to be a mock function, and instead calling done() at the end of your expect statements.
test('should return status 201 and msg', done => {
try {
const fun = handlers.handleRegister()
// res only exists inside of the `try`
const res = {
status: jest.fn().mockReturnThis(),
send: jest.fn() // << is now a mock function
}
fun({ body: { email: 'a', password: 'a' } }, res)
expect(res.status).toBeCalledWith(201)
// here `res.send` is now defined, and you can use `toBeCalledWith`
expect(res.send).toBeCalledWith({ msg: 'User registration achieved successfully' })
done();
} catch(err) {
done(err)
}
})

Angular/RxJS Observable piping from multiple functions

So I have a http get function defined in a Service class that gets an object from an endpoint and does some processing on the result:
public get(url: string, params: {}): Observable<Object> {
return this.http.get<IResult>(this.endpointRootUrl() + url, {
params: params
}).pipe(
map(res => {
if (res.Result !== 0)
throw new Error(res.Message)
else
return res.Object;
}),
catchError((err: HttpErrorResponse) => throwError(err.statusText))
)
}
This function is called from an ExampleService which gets the observable and does some more processing on it:
public loadData(): Observable<IData[]> {
return this.get("/DataLink/ListData", {}).pipe(
map(res => { return <IData[]>res }),
catchError((err: string) => throwError("There was an error retrieving data: " + err))
)
}
My questions are:
In the http get function where I check the Result property of the IResult object that gets returned from the back-end, I throw an error if the value of the result is not what is expected.
The issue is, the Message property does not get properly sent into the catchError part of the loadData function; the error message returns as "There was an error retrieving data: undefined". What am I doing wrong here?
Is this code an acceptable way to achieve what I'm trying to do? I'm open to suggestions/critique.
Thanks in advance for any help offered.
Try to move catchError before all another pipes, exept takeUntil
e.g.
Inside component:
return this.apiService.changeStatus(id, status)
.pipe(
catchError((error: HttpErrorResponse) => {
this.showMsg('error', error.error.message);
return throwError(error);
}),
tap((data: SomeResponseModel) => {
this.status = data;
})
);
Inside service
changeStatus(id: number, status: StatusesEnum): Observable<SomeModel> {
const payload: { status: StatusesEnum} = { status };
return this.http.patch<ResponseModel<SomeModel>>(this.apiUrl(`/${id}/status`), payload)
.pipe(map((data: ResponseModel<SomeModel>) => new SomeModel(data.data)));
}
In the http get function where I check the Result property of the
IResult object that gets returned from the back-end, I throw an error
if the value of the result is not what is expected. The issue is, the
Message property does not get properly sent into the catchError part
of the loadData function; the error message returns as "There was an
error retrieving data: undefined". What am I doing wrong here?
Error object doesnt have statusText property
I think you are looking for message property
catchError((err: Error) => throwError(err.message))
With that change rest of code works fine.
Example
onst data = {
Message: "Random error",
Result: 1,
Object: {
test: 1
}
}
function get(data) {
return rxjs.of(data).pipe(
rxjs.operators.map(res => {
if (res.Result !== 0) {
throw new Error(res.Message)
} else {
return res.Object;
}
}),
rxjs.operators.catchError((err) => {
return rxjs.throwError(err.message)
})
)
}
function loadData(data) {
return get(data).pipe(
rxjs.operators.catchError(err => {
return rxjs.throwError(`There was an error retrieving data: "${err}"`)
})
)
}
loadData(data).subscribe({
next: (value) => {
console.log(value)
},
error: (error) => {
console.log('error ', error)
},
complete: () => {
console.log('completed ')
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>

How can I make sure that this function handles errors properly?

I have a function that checks user input in an express application. I don't want to use any library to validate those inputs so I declared an array where errors are pushed into.
I have embedded the middleware function as a static method in a class...
static postAdchecker(req, res, next) {
let { state, price, manufacturer, model, bodytype } = req.body;
console.log('req', req.body);
const errors = [];
// If state is empty or undefined throw this error
if (!state) {
console.log('state', state);
const error = {
message: 'Please specify the state of the car'
};
errors.push(error);
}
// If state is supplied, convert it to lowercase, trim and check if value is new/used
if (state.toLowerCase().trim() !== 'new' && state.toLowerCase().trim() !== 'used') {
const error = {
message: 'State can only be new or used'
};
errors.push(error);
}
// Same goes for the others.
if (!price) {
const error = {
message: 'You will need to specify a sale price'
};
errors.push(error);
}
if (!manufacturer) {
const error = {
message: 'Specify a manufacturer'
};
errors.push(error);
}
if (!model) {
const error = {
message: 'Specify a model'
};
errors.push(error);
}
if (!bodytype) {
const error = {
message: 'You will need to specify a bodytype'
};
errors.push(error);
}
return res.status(400).json({
status: 400,
errors: {
body: errors.map(err => err.message)
}
});
console.log('errors', errors);
req.body.state = state.toLowerCase().trim();
req.body.price = price.toLowerCase().trim();
req.body.manufacturer = manufacturer.toLowerCase().trim();
req.body.model = model.toLowerCase().trim();
req.body.bodytype = bodytype.toLowerCase().trim();
// req.authData;
return next();
}
How can I achieve the following?
Convert the values in the input field to lowercase and trim when supplied.
When there are errors, return all the errors.
When there are no errors, transfer operation to the next function instead of returning an empty array.
You are just missing one condition:
if(errors.length) { // <<<
return res.status(400).json({
status: 400,
errors: {
body: errors.map(err => err.message)
}
});
}

Categories

Resources