I'm trying to do this test, for a simple function, but I can't. I posted my test code and error.
I tried to do it in several different ways but I was not successful.
I'm using NestJS CLI and the test using jestJs
// My coding
createSession(login: string, password: string) {
const search: UserEntity = this.users.find(
(user: UserEntity) => user.cpf === login,
);
if (search) return search.senha === password;
else throw new HttpException('UNAUTHORIZED', HttpStatus.UNAUTHORIZED);
}
// My test
it('should retrieve getHello', async () => {
await expect(
service.createSession(mockLogin.login, mockLogin.password),
).rejects.toEqual(
new HttpException('UNAUTHORIZED', HttpStatus.UNAUTHORIZED),
);
});
// The error
● LoginService › Get service › should retrieve getHello
HttpException: UNAUTHORIZED
22 | );
23 | if (search) return search.senha === password;
> 24 | else throw new HttpException('UNAUTHORIZED', HttpStatus.UNAUTHORIZED);
| ^
25 | }
26 | }
27 |
I managed to solve it as follows:
it('should retrieve', async () => {
await expect(
service.createSession(mockLogin.login, mockLogin.password),
).rejects.toThrow();
});
Related
I've created a Jest custom matcher. It works (meaning, it passes/fails when it should), but I don't see the message anywhere in Jest's output.
What am I doing wrong? Do I have to do something to "enable" messages? Am I totally misunderstanding where the message is supposed to show up?
Environment: NestJS, Prisma
Execution command: jest --watch
Simplified code:
declare global {
namespace jest {
interface Matchers<R> {
toMatchHash(received: string, expected: string): R;
}
}
}
expect.extend({
toMatchJsonHash(received, expected) {
return {
pass: false,
message: () => `Why doesn't this work?!`,
};
},
});
expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
Output:
● MyService › should pass a test using a custom matcher
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: toMatchJsonHash<db0110285c148c77943f996a17cbaf27>
Received: {<Big ol' object redacted for conciseness>}
Number of calls: 1
178 |
179 | // #ts-ignore
> 180 | expect(prisma.name.findMany).toHaveBeenCalledWith(expect.toMatchJsonHash('db0110285c148c77943f996a17cbaf27'));
| ^
181 | // expect(prisma.name.findMany).toHaveBeenCalledWith({
182 | // select: { type: true, name: true },
183 | // where: {
at Object.<anonymous> (my/my.service.spec.ts:180:32)
I'm expecting to see "Why doesn't this work?!" somewhere in the output, but I don't. What am I missing?
As suggested by #jonsharpe, the reason was that Jest was showing the message from the "outer" matcher, .toHaveBeenCalledWith().
To fix this, I found the source that defines the .toHaveBeenCalledWith() matcher and "merged" its code into my custom matcher.
This enabled my custom matcher to effectively "extend" the functionality of the .toHaveBeenCalledWith() matcher, including my own custom code and messages.
In case it helps someone, the code I ended up with for my specific use case was:
declare global {
namespace jest {
interface Matchers<R> {
toHaveBeenCalledWithObjectMatchingHash(expected: string): CustomMatcherResult;
}
}
}
expect.extend({toHaveBeenCalledWithObjectMatchingHash(received, expected) {
const isSpy = (received: any) =>
received != null &&
received.calls != null &&
typeof received.calls.all === 'function' &&
typeof received.calls.count === 'function';
const receivedIsSpy = isSpy(received);
const receivedName = receivedIsSpy ? 'spy' : received.getMockName();
const calls = receivedIsSpy
? received.calls.all().map((x: any) => x.args)
: received.mock.calls;
if(calls.length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was not called.`,
};
}
if(calls[0].length === 0) {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the function was called, but not with any arguments.`,
};
}
const md5Hash = crypto.createHash('md5');
const receivedHash = md5Hash.update(JSON.stringify(calls[0][0])).digest('hex');
const pass = receivedHash === expected;
if(pass) {
return {
pass: true,
message: () => `expected the function to not be called with an object that hashes to '${expected}'. Instead, the passed object hashes to the same value.`,
};
} else {
return {
pass: false,
message: () => `expected the function to be called with an object that hashes to '${expected}'. Instead, the passed object hashes to '${receivedHash}'.`,
};
}
}});
I am trying to mock Stripe's class constructor, but it does not seem to be working as it should according to Jest's docs. I followed these instructions in order to mock to the constructor.
Here is the controller code that uses Stripe (/src/controllers/stripe.ts):
import Stripe from 'stripe';
/**
* Creates a Stripe Customer
*
* #param {object} req The express request object.
* #param {object} res The express response object.
* #param {function} next The express next function.
*/
export const createStripeCustomer = async (
req: Request,
res: Response,
next: NextFunction
): Promise<Response | void> => {
const stripeApiKey: string = process.env.STRIPE_API_KEY ? process.env.STRIPE_API_KEY : '';
const user: IUser = req.user as IUser;
/** Send error if no Stripe API key or no user */
if (!stripeApiKey) {
return next(
'Something went wrong. If this problem persists, please contact technical support.'
);
} else if (!user) {
res.status(401);
return next('Unauthroized Access');
}
const stripe = new Stripe(stripeApiKey, {
apiVersion: '2020-03-02',
});
};
Here is the test code (/src/tests/controllers/stripe.ts):
import Stripe from 'stripe';
/**
* Return error if stripe.customer.create fails
*/
test('Should error if stripe.customer.create fails', async (done) => {
process.env.STRIPE_API_KEY = 'StripeAPIKey';
jest.mock('stripe', () => {
return jest.fn().mockImplementation(function () {
return {
customers: {
create: jest.fn().mockRejectedValue('Stripe error'),
},
};
});
});
mockRequest = ({
body: {
payment_method: mockPaymentMethodObject,
},
user: {
email: 'jdoe#google.com',
first_name: 'John',
last_name: 'Doe',
},
} as unknown) as Request;
await createStripeCustomer(mockRequest, mockResponse, mockNextFunction);
expect(mockNextFunction).toHaveBeenCalledTimes(1);
expect(mockNextFunction).toHaveBeenCalledWith('Stripe error');
done();
});
My expectation is that the test should pass, but I am getting the following error:
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: "Stripe error"
Received: [Error: Invalid API Key provided: StripeAPIKey]
Number of calls: 1
137 |
138 | expect(mockNextFunction).toHaveBeenCalledTimes(1);
> 139 | expect(mockNextFunction).toHaveBeenCalledWith('Stripe error');
| ^
140 | done();
141 | });
142 | test('Should error if user update fails', () => {});
at _callee3$ (src/tests/controllers/stripe.test.ts:139:30)
at tryCatch (node_modules/regenerator-runtime/runtime.js:45:40)
at Generator.invoke [as _invoke] (node_modules/regenerator-runtime/runtime.js:274:22)
at Generator.prototype.<computed> [as next] (node_modules/regenerator-runtime/runtime.js:97:21)
at asyncGeneratorStep (node_modules/#babel/runtime/helpers/asyncToGenerator.js:3:24)
at _next (node_modules/#babel/runtime/helpers/asyncToGenerator.js:25:9)
This seems to be throwing because Stripe is not being mocked and it is trying to authenticate with the API key of StripeAPIKey.
Update 1:
I also tried the following, which led to the same result:
import * as stripe from 'stripe';
jest.mock('stripe', () => {
return {
Stripe: jest.fn().mockImplementation(() => ({
customers: {
create: jest.fn().mockRejectedValueOnce('Stripe error'),
},
})),
};
});
I found this post and I'm not exactly sure why this worked instead of using jest.mock(), but here is how I got the tests to start passing. I created a separate file in /tests/__mocks__/.
/tests/mocks/stripe.js (Note: this is not a TS file)
export default class Stripe {}
and here is my new test in /tests/src/controllers/stripe.ts
/**
* Return error if stripe.customer.create fails
*/
test('Should error if stripe.customer.create fails', async (done) => {
Stripe.prototype.customers = ({
create: jest.fn().mockRejectedValueOnce('Stripe error'),
} as unknown) as Stripe.CustomersResource;
await createStripeCustomer(mockRequest, mockResponse, mockNextFunction);
expect(Stripe.prototype.customers.create).toHaveBeenCalledTimes(1);
expect(mockNextFunction).toHaveBeenCalledTimes(1);
expect(mockNextFunction).toHaveBeenCalledWith('Stripe error');
done();
});
If someone with more Jest/Typescript knowledge wants to shed some light on this. That would be great!
I am setting up a graphql server with graphql-yoga and `prisma using Typescript. When a user signs up, an email with a link for validation will be sent to the given email address.
Everything is working fine, but i want to write a test for the mutation before refactoring the functionality, which checks if the 'send' function of SendGrid hast been called.
I tried spying on the function with jest.spyOn, but all I get is an error, that comes from not providing an API key for SendGrid in the tesing environment.
I have used spyOn before, and it worked, though this is the first time I am using jest with Typescript.
SignUp Mutation
import * as sgMail from '#sendgrid/mail';
sgMail.setApiKey(process.env.MAIL_API_KEY);
export const Mutation = {
async signUpUser(parent, { data }, { prisma }, info) {
[...]
const emailData = {
from: 'test#test.de',
to: `${user.email}`,
subject: 'Account validation',
text: `validation Id: ${registration.id}`
};
await sgMail.send(emailData);
return user;
}
}
Trying spyOn
import * as sgMail from '#sendgrid/mail';
const signUpUserMutation = gql`
mutation($data: ValidationInput) {
signUpUser (data: $data) {
id
email
}
}
`;
it('should send a registration email, with a link, containing the id of the registration', async () => {
spyOn(sgMail, "send").and.returnValue(Promise.resolve('Success'));
const variables = {
data: {
email: "test#test.de",
password: "anyPassword"
}
};
await client.mutate({ mutation: signUpUserMutation, variables});
expect(sgMail.send).toHaveBeenCalled();
});
Running the test gives me:
Error: GraphQL error: Unauthorized
Commenting out the function call of send in the mutation and running the test gives me:
Error: expect(spy).toHaveBeenCalled()
Expected spy to have been called, but it was not called.
You don't mock #sendgrid/mail module in a correct way. That's why the error happened. Here is the solution without using GraphQL test client. But you can use GraphQL test client to test your GraphQL resolver and GraphQL Schema after you mock #sendgrid/mail module correctly.
mutations.ts:
import * as sgMail from '#sendgrid/mail';
sgMail.setApiKey(process.env.MAIL_API_KEY || '');
export const Mutation = {
async signUpUser(parent, { data }, { prisma }, info) {
const user = { email: 'example#gmail.com' };
const registration = { id: '1' };
const emailData = {
from: 'test#test.de',
to: `${user.email}`,
subject: 'Account validation',
text: `validation Id: ${registration.id}`
};
await sgMail.send(emailData);
return user;
}
};
mutations.spec.ts:
import { Mutation } from './mutations';
import * as sgMail from '#sendgrid/mail';
import { RequestResponse } from 'request';
jest.mock('#sendgrid/mail', () => {
return {
setApiKey: jest.fn(),
send: jest.fn()
};
});
describe('Mutation', () => {
describe('#signUpUser', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should send a registration email, with a link, containing the id of the registration', async () => {
(sgMail.send as jest.MockedFunction<typeof sgMail.send>).mockResolvedValueOnce([{} as RequestResponse, {}]);
const actualValue = await Mutation.signUpUser({}, { data: {} }, { prisma: {} }, {});
expect(actualValue).toEqual({ email: 'example#gmail.com' });
expect(sgMail.send).toBeCalledWith({
from: 'test#test.de',
to: 'example#gmail.com',
subject: 'Account validation',
text: `validation Id: 1`
});
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/56379585/mutations.spec.ts (12.419s)
Mutation
#signUpUser
✓ should send a registration email, with a link, containing the id of the registration (23ms)
--------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
mutations.ts | 100 | 100 | 100 | 100 | |
--------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.315s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/56379585
I have this node module called client. It has the following structure:
//index.js
import users from "./users"
export default { users }
And:
//users.js
export default { getUser }
function getUser(username) {
...
return {role: userRole}
}
I want to mock this getUser(username) function in my tests.
So I could call something like:
client.users.getUser.mockResolvedValueOnce({role: "manager"})
My test header is like:
let client = jest.genMockFromModule('client').default;
client.users.getUser = jest.fn();
But, running my test, I get the following error when my original code call client.users.getUser.
TypeError: Cannot read property 'users' of undefined
58 |
59 | // Retrieve user and then return its projects
> 60 | client.users.getUser(username)
| ^
61 | .then(user => {
62 | if (!user) {
63 | throw new Error(`User ${username} not found`)
at Object.getUser (node_modules/client/build/users.js:26:45)
at Object.getUser [as insert] (src/web/controller/projects.js:60:18)
at Object.insert (src/web/controller/projects.test.js:80:18)
You can just mock the //users.js module like this:
jest.mock('./users.js', () => ({getUser: () => ({role: "manager"})})) //note that the path is relative to the test
if you need different return values during your tests you can mock it to return a spy and set the mock return value in the tests:
import {getUsers} from './users'
jest.mock('./users.js', () => ({getUser: jest.fn()}))
it('does something', () => {
getUser.mockImplementation(() => ({role: "manager"}))
})
it('does something else', () => {
getUser.mockImplementation(() => ({role: "user"}))
})
I'm trying to do an integration test on a function that manages users. The calls it makes are asynchronous but Jest seems to be cutting out after I add the test user and check the details.
it("runs lifecycle", async done => {
// expect.assertions(1);
function check(a, b, c) {
return new Promise((resolve, reject) => {
console.log(a, b, c);
if (a != b) reject(c);
console.error(a, b, c);
resolve();
});
}
await new Promise((resolve, reject) => {
user.add(testUser).then(() => {
user.get(testUser.id).then(out => {
check(out, testUser, "adding").then(() => {
user.update(testUser1).then(() => {
user
.get(testUser1.id)
.then(out => check(out, testUser1, "update"))
.then(() => {
user.delete(testUser.id).then(() => {
user
.get(testUser1.id)
.then(out => check(out, null, "delete"))
.then(() => {
resolve();
done();
});
});
});
});
});
});
});
}).then(() => {
expect(true).toBeTruthy;
done();
});
done();
testUser and testUser1 are objects with some of their properties changed. I have tested this outside of Jest and the code runs fine.
console.log user.js:68
Successfully created new user: test
console.log user.test.js:26
{ id: 'test',
email: 'you#me.com',
first: 'Wilhelm',
last: 'Erasmus',
phone: '+271234567' } { id: 'test',
email: 'yo#me.com',
first: 'Wilhelm',
last: 'Erasmus',
phone: '+277654321' } 'adding'
console.error user.test.js:28
{ id: 'test',
email: 'you#me.com',
first: 'Wilhelm',
last: 'Erasmus',
phone: '+271234567' } { id: 'test',
email: 'yo#me.com',
first: 'Wilhelm',
last: 'Erasmus',
phone: '+277654321' } 'adding'
FAIL ./user.test.js (7.269s)
User APIs
✕ runs lifecycle (2779ms)
● User APIs › runs lifecycle
Error
adding
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | Unknown | Unknown | Unknown | Unknown | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 9.916s
Ran all test suites.
console.log user.js:114
Successfully deleted test
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
I fixed the problem. I'm not sure why this solution works and I am happy to accept an answer that explains it better.
var addUser = user.add(testUser);
var checkAdd = addUser
.then(() => user.get(testUser.id))
.then(check1 => check(check1, testUser, "Add"));
var updateUser = checkAdd.then(() => user.update(testUser1));
var checkUpdate = updateUser
.then(() => user.get(testUser1.id))
.then(check2 => check(check2, testUser1, "Add"));
var deleteUser = checkUpdate.then(() => user.delete(testUser1.id));
var checkDelete = deleteUser.then(() => {
expect(user.exists).resolves.toBeFalsy;
});
return Promise.all([
addUser,
checkAdd,
updateUser,
checkUpdate,
deleteUser,
checkDelete
]);