Test functions cannot both take a 'done' callback - javascript

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

Related

Jest test of function that contain an async call to User.findAll [sequelize postgresql] fails?

I’m trying to add a test to getAllUsers function but I don’t know why the test is failing?
I think that the assertions are run before all the async call to User.findAllfinishes!!!
do you have any suggestions?
this is the file being tested:
const { Op } = require('sequelize')
const { User } = require('../models')
const catchAsync = require('../utils/catchAsync')
const AppError = require('../utils/appError')
exports.getAllUsers = catchAsync(async (req, res, next) => {
const users = await User.findAll({
attributes: ['id', 'username', 'email', 'role', 'avatar'],
where: {
id: { [Op.gt]: 0 }
}
})
if (!users.length) {
return next(new AppError('no data found', 204))
}
res.status(200).json({
status: 'success',
data: users
})
})
and this is the test code:
const userController = require('../controllers/userController')
describe('UserController', () => {
const users = [
{
username: 'Admin',
role: 'admin',
avatar: 'bb',
email: 'admin#gmail.com'
},
{
username: 'User',
role: 'user',
avatar: 'bb',
email: 'user#gmail.com'
}
]
test('Expect to respond with 200 code and users data', async () => {
const req = {}
const res = { status: jest.fn(() => res), json: jest.fn(() => res) }
const next = jest.fn()
await userController.getAllUsers(req, res, next)
expect(res.status).toHaveBeenCalledTimes(1)
expect(res.status).toHaveBeenCalledWith(200)
expect(res.json).toHaveBeenCalledTimes(1)
expect(res.json).toHaveBeenCalledWith({
status: 'success',
data: users
})
})
})
thanks for the help
Considering there's no pre-requisite (mocked database) that hasn't been shared in the question
When you run jest --coverage it will generate you a coverage report which would give you an overview of the code execution
above you can see that the if statement has not been executed, meaning that an error is thrown from User.findAll
To solve this: you can mock the User model and it's findAll's resolved value to overcome this
const userController = require('../controllers/userController');
const { User } = require('../models');
// generate auto-mock of the module
jest.mock('../models');
describe('UserController', () => {
const users = [
// .... same as in the question
];
beforeAll(() => {
// give the mock function a value
// for the promise to be resolved with
User.findAll.mockResolvedValue(users);
});
test('Expect to respond with 200 code and users data', async () => {
// .... same as in the question
});
});

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

How to mock an async action creator in redux with jest

I'm trying to write a unit test for a redux async action creator using jest.
asyncActions.js:
const startSignInRequest = () => ({
type: START_SIGNIN_REQUEST
});
// action creator to dispatch the success of sign In
export const signInSucceded = user => ({
type: SIGNIN_USER_SUCCEEDED,
user
});
// action creator to dispatch the failure of the signIn request
export const signInFailed = error => ({
type: SIGNIN_USER_FAILED,
error
});
const signInUser = user => dispatch => {
dispatch(startSignInRequest);
return signInApi(user).then(
response => {
const { username, token } = response.data;
dispatch(signInSucceded(username));
localStorage.setItem("token", token);
history.push("/homepage");
},
error => {
let errorMessage = "Internal Server Error";
if (error.response) {
errorMessage = error.response.data;
}
dispatch(signInFailed(errorMessage));
dispatch(errorAlert(errorMessage));
}
);
};
signInApi.js:
import axios from "axios";
import { url } from "../../env/config";
const signInApi = async user => {
const fetchedUser = await axios.post(`${url}/signIn`, {
email: user.email,
password: user.password
});
return fetchedUser;
};
In the Writing tests of redux's official documentation, they use fetch-mock library. However, I think that this library call the real Api.
I tried to mock the axios api using jest mocks.
/__mocks/signInApi.js:
const users = [
{
login: 'user 1',
password: 'password'
}
];
export default function signInApi(user) {
return new Promise((resolve, reject) => {
const userFound = users.find(u => u.login === user.login);
process.nextTick(() =>
userFound
? resolve(userFound)
// eslint-disable-next-line prefer-promise-reject-errors
: reject({
error: 'Invalid user credentials',
}),
);
});
}
__tests/asyncActions.js:
jest.mock('../axiosApis/signInApi');
import * as actions from '../actions/asyncActions';
describe('Async action creators', async () => {
it('Should create SIGN_IN_USER_SUCCEEDED when signIn user has been done', () => {
const user = {
login: 'user 1',
password: 'password'
}
await expect(actions.signInUser(user)).resolves.toEqual({
user
})
})
});
The test failed and I got:
expect(received).resolves.toEqual()
Matcher error: received value must be a promise
Received has type: function
Received has value: [Function anonymous]
How can I mock this async action creator only with jest?
Looks like you need to update your mock to resolve to an object like this:
export default function signInApi(user) {
return new Promise((resolve, reject) => {
const userFound = users.find(u => u.login === user.login);
process.nextTick(() =>
userFound
? resolve({ // <= resolve to an object
data: {
username: 'the username',
token: 'the token'
}
})
// eslint-disable-next-line prefer-promise-reject-errors
: reject({
error: 'Invalid user credentials',
}),
);
});
}
...then what you are really testing is that actions.signInUser returns a function which can be called with a user...
...which then returns another function which can be called with a dispatch which dispatches the proper actions:
jest.mock('./signInApi');
import * as actions from './asyncActions';
describe('Async action creators', () => {
it('Should create SIGN_IN_USER_SUCCEEDED when signIn user has been done', async () => {
const user = {
login: 'user 1',
password: 'password'
};
const dispatch = jest.fn();
await actions.signInUser(user)(dispatch); // <= call the function on a user, then call the resulting function on a dispatch
expect(dispatch).toHaveBeenCalledTimes(2); // Success!
expect(dispatch).toHaveBeenNthCalledWith(1, { type: START_SIGNIN_REQUEST }); // Success!
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SIGNIN_USER_SUCCEEDED, user: 'the username' }); // Success!
})
});
EDIT: I have to edit my answer as the first one pointed to a wrong direction.
So from my understanding you want to mock the Action + Return value. In your case I would just immediately return the result of your mock function. As you're not mocking axios.post you don't need to wrap everything inside a promise and return that. You're not mocking only the HTTP call but the whole action.
const users = [
{
login: 'user 1',
password: 'password'
}
];
export default function signInApi(user) {
const userFound = users.find(u => u.login === user.login);
return (userFound ? userFound : {
error: 'Invalid user'
});
}

How to skip a promise that doesn't work?

async function getData() {
const getProject = await axios.get('url', {
auth: {
username: 'username',
password: 'pw'
}
});
const projects = getProject.data.value;
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
})
);
const results = await axios.all(promises)
const KPIs = results.map(v => v.data.value);
}
What I am trying to do is:
get an axios call to fetch the name of the projects.
Use those fetched names to call multiple axios calls. This is supposed to give me some data from that project.
When I do the second axios call, there are some API calls that won't work because the data does not exist in that project. This was intended. But it will break by giving me the 404 error and won't reach the line const results = await axios.all(promises).
I want it to skip that whenever it doesn't exist and only call it from the one that exists and store the fetched data into KPIs. How can I do that?
EDIT
The first axios call returns an array of objects
ex)
{id: "id...", name: "Javelin", url: " .visualstudio.com/_apis/projects/6a93eab2-4996-4d02-8a14-767f02d94993", state: "wellFormed", revision: 99, …}
Something like that. So, I literally just get the .name of this object and put it into my url like:
axios.get({id}.extmgmt.visualstudio.com/_apis/ExtensionManagement/InstalledExtensions/{id}/..../${project.name}_test/Documents
This wraps the call in a promise that won't fail.
You also need to consider than you probably want to filter out null responses.
async function getData() {
const getProject = await axios.get('url', {
auth: {
username: 'username',
password: 'pw'
}
});
const projects = getProject.data.value;
const promises = projects.map(fallible);
const results = await axios.all(promises)
const KPIs = results.filter(v => v).map(v => v.data.value);
}
function fallibleCall(project) {
return new Promise((resolve, reject) => {
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).then(resolve).catch(resolve);
})
}
If the problem is with fetching project data you just need to handle errors that occur there, one way is to simply catch the error and return null instead.
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).catch(err => {
return null;
})
);
You can easily fix that by handling 404 responses by yourself and keep rejecting for all other errors as follows:
const promises = projects.map(project =>
axios.get(`url`, {
auth: {
username: 'username',
password: 'pw'
}
}).catch(err => {
if (err.response.status === 404) {
return null; // or an empty array or whatever you want
}
throw err;
});
);
See https://github.com/axios/axios#handling-errors for details.

asserting against thrown error objects in jest

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

Categories

Resources