I am stuck on writing a unit test for when the redisClient fails for the createClient call. Any idea on how I can write this. Below you will find what I have so far.
const asyncRedis = require("async-redis");
class redis {
constructor(redisHost, redisPort) {
this.redisHost = redisHost;
this.redisPort = redisPort;
}
async init() {
try {
this.redisClient = asyncRedis.createClient({
port: this.redisPort,
host: this.redisHost
});
} catch(error) {
console.log(`Error creating client due to: ${error}`)
}
}
}
module.exports = redis;
redis-test.js
test('init on error', async () => {
jest.mock('../../src/redis/redis')
const redis = require('../../src/redis/Redis');
redis.mockImplementation(() => {
return {
init: jest.fn(() => { throw new Error(); }
)};
})
expect(await redis.init()).toThrowError(Error());
})
You are mocking your code, you should be mocking the async-redis library code.
You need to mock the createClient method to always throw an error. So that you can check your catch flow is executed.
This part, you got it right jest.fn(() => { throw new Error(); }, always return an error.
I am no expert in NodeJS, sorry I am not able to provide detailed source code.
Related
I'm testing my GraphQL api using Jest.
I'm using a separate test suit for each query/mutation
I have 2 tests (each one in a separate test suit) where I mock one function (namely, Meteor's callMethod) that is used in mutations.
it('should throw error if email not found', async () => {
callMethod
.mockReturnValue(new Error('User not found [403]'))
.mockName('callMethod');
const query = FORGOT_PASSWORD_MUTATION;
const params = { email: 'user#example.com' };
const result = await simulateQuery({ query, params });
console.log(result);
// test logic
expect(callMethod).toBeCalledWith({}, 'forgotPassword', {
email: 'user#example.com',
});
// test resolvers
});
When I console.log(result) I get
{ data: { forgotPassword: true } }
This behaviour is not what I want because in .mockReturnValue I throw an Error and therefore expect result to have an error object
Before this test, however, another is ran
it('should throw an error if wrong credentials were provided', async () => {
callMethod
.mockReturnValue(new Error('cannot login'))
.mockName('callMethod');
And it works fine, the error is thrown
I guess the problem is that mock doesn't get reset after the test finishes.
In my jest.conf.js I have clearMocks: true
Each test suit is in a separate file, and I mock functions before tests like this:
import simulateQuery from '../../../helpers/simulate-query';
import callMethod from '../../../../imports/api/users/functions/auth/helpers/call-accounts-method';
import LOGIN_WITH_PASSWORD_MUTATION from './mutations/login-with-password';
jest.mock(
'../../../../imports/api/users/functions/auth/helpers/call-accounts-method'
);
describe('loginWithPassword mutation', function() {
...
UPDATE
When I substituted .mockReturnValue with .mockImplementation everything worked out as expected:
callMethod.mockImplementation(() => {
throw new Error('User not found');
});
But that doesn't explain why in another test .mockReturnValue works fine...
Change .mockReturnValue with .mockImplementation:
yourMockInstance.mockImplementation(() => {
throw new Error();
});
in case you want to assert
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
If it's a promise you can also to .rejects www.jestjs.io/docs/en/asynchronous#resolves--rejects
For promises, can use https://jestjs.io/docs/mock-function-api#mockfnmockrejectedvaluevalue
test('async test', async () => {
const asyncMock = jest.fn().mockRejectedValue(new Error('Async error'));
await asyncMock(); // throws "Async error"
});
For testing that error was thrown or not, can use https://eloquentcode.com/expect-a-function-to-throw-an-exception-in-jest
const func = () => {
throw new Error('my error')
}
it('should throw an error', () => {
expect(func).toThrow()
})
For Angular + Jest:
import { throwError } from 'rxjs';
yourMockInstance.mockImplementation(() => {
return throwError(new Error('my error message'));
});
Hi I am using sequelize ORM with Postgres database for this node.js express.js app. As for testing I am using mocha, chai and sinon.
I am trying to complete a test for a class's method. The class instant i call it userService and the method is findOneUser .. This method has got an argument id .. So in this moment I want to test for a throw error the test works if I dont put an argument. That means this test is obviously not complete.
Here is the class method I want to test
userService.js
module.exports = class UserService {
async findOneUser(id) {
try {
const user = await User.findOne({ where: { id: id } }); // if null is returned error is thrown
if (!user) {
throw createError(404, "User not found");
}
return user;
} catch (err) {
throw err
}
}
}
And here is my test code
userService.spec.js
describe.only("findOne() throws error", () => {
let userResult;
const error = customError(404, "User not found"); // customError is a function where I am throwing both status code and message
before("before hook last it block issue withArgs" , async () => {
// mockModels I have previously mocked all the models
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).resolves(null); // basically have this called and invoked from calling the method that it is inside of based on the argument fakeId
userResult = sinon.stub(userService, "findOneUser").throws(error); // 🤔this is the class instances method I wonder how to test it withArguments anything I try not working but SEE BELOW COMMENTS🤔
});
after(() => {
sinon.reset()
});
it("userService.findOneUser throws error works but without setting arguments 🤔", () => {
expect(userResult).to.throw(error);
});
/// this one below still not working
it("call User.findOne() with incorrect parameter,, STILL PROBLEM 🤯", () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
});
But for the method of my class findOneUser has an argument (id) how can I pass that argument into it where I am stubbing it?
Or even any ideas on how to fake call the class method?? I want both it blocks to work
EDIT
I forgot to mention I have stubbed the mockModels.User already and that was done before the describe block
const UserModel = {
findByPk: sinon.stub(),
findOne: sinon.stub(),
findAll: sinon.stub(),
create: sinon.stub(),
destroy: sinon.stub()
}
const mockModels = makeMockModels( { UserModel } );
// delete I am only renaming UserModel to User to type things quicker and easier
delete Object.assign(mockModels, {['User']: mockModels['UserModel'] })['UserModel']
const UserService = proxyquire(servicePath, {
"../models": mockModels
});
const userService = new UserService();
const fakeUser = { update: sinon.stub() }
SOLUTION
I think this might be the solution to my problem strange but it works the tests is working with this
describe.only("findOne() throws error", async () => {
const errors = customError(404, "User not found"); // correct throw
const errors1 = customError(404, "User not foundss"); // on purpose to see if the test fails if should.throw(errors1) is placed instead of should.throw(errors)
after(() => {
sinon.reset()
});
// made it work
it("call User.findOne() with incorrect parameter, and throws an error, works some how! 🤯", async () => {
userResult = sinon.spy(userService.findOneUser);
try {
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
await userResult(fakeId);
} catch(e) {
// pass without having catch the test fails 😵💫
} finally {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
}
});
it("throws error user does not exist,,, WORKS", () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw
mockModels.User.findOne.withArgs(fakeId).should.throw(errors); // specially this part without having the catch test fails. but now test works even tested with errors1 variable
expect(userResult).to.throw;
});
});
MORE CLEANER SOLUTION BELOW
I like this solution more as I call the method inside within the describe block, then I do the test of the two it blocks.
The problem was I should not have stubbed the class method but should have called it directly, or used sinon.spy on the method and call it through the spy. As for checking that the errors are as expected then running this line of expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors); was the solution I needed.
describe.only('findOne() user does not exists, most cleanest throw solution', () => {
after(() => {
sinon.reset()
});
const errors = customError(404, "User not found");
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
userResult = sinon.spy(userService, "findOneUser"); // either invoke the through sinon.spy or invoke the method directly doesnt really matter
userResult(fakeId);
// userResult = userService.findOneUser(fakeId); // invoke the method directly or invoke through sinon.spy from above
it('call User.findOne() with invalid parameter is called', () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
it('test to throw the error', () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors);
expect(userResult).to.throw;
})
});
utils file
const isStatusError = (err: any): err is StatusError =>
err.status !== undefined;
export const handleError = async (err: any, emailer?: Mailer) => {
const sendErrorEmail = async (
subject: string,
text: string,
emailer?: Mailer
) => {
try {
const mail: Pick<Mail, "from" | "to"> = {
from: config.email.user,
to: config.email.user,
};
// 2. This throws an error
await emailer?.send({ ...mail, subject, text });
} catch (err) {
// 3. It should call this function recursively...
await handleError(new EmailError(err), emailer);
}
};
if (isStatusError(err)) {
if (err instanceof ScrapeError) {
console.log("Failed to scrape the website: \n", err.message);
}
if (err instanceof AgendaJobError) {
console.log("Job ", err.message);
// #TODO
}
if (err instanceof RepositoryError) {
console.log("Repository: ");
console.log(err.message);
// #TODO
}
// 4. and eventually come here and end the test...
if (err instanceof EmailError) {
console.log("Failed to create email service", err);
}
// 1. It goes here first.
if (err instanceof StatusError) {
console.log("generic error", err);
await sendErrorEmail("Error", "", emailer);
}
} else {
if (err instanceof Error) {
console.log("Generic error", err.message);
}
console.log("Generic error", err);
}
};
test file
import * as utils from "./app.utils";
import { Mailer } from "./services/email/Emailer.types";
import { StatusError } from "./shared/errors";
const getMockEmailer = (implementation?: Partial<Mailer>) =>
jest.fn<Mailer, []>(() => ({
service: "gmail",
port: 5432,
secure: false,
auth: {
user: "user",
pass: "pass",
},
verify: async () => true,
send: async () => true,
...implementation,
}))();
describe("error handling", () => {
it("should handle email failed to send", async () => {
const mockEmailer = getMockEmailer({
send: async () => {
throw new Error();
},
});
// This line is the problem. If I comment it out, it's all good.
const spiedHandleError = jest.spyOn(utils, "handleError");
// #TODO: Typescript will complain mockEmailer is missing a private JS Class variable (e.g. #transporter) if you remove `as any`.
await utils.handleError(new StatusError(500, ""), mockEmailer as any);
expect(spiedHandleError).toBeCalledTimes(2);
});
});
This test runs forever, and it is because I made handleError a spy function.
I tried to import itself and run await utils.handleError(new EmailError(err), emailer) but it still continue to hang.
So what happens is:
It throws an Error.
It will then figure out it is a StatusError which is a custom error, and it will output the error and call a function to send an email.
However, attempting to send an email throws another Error
It should then call itself with EmailError
It will detect it is an EmailError and only output the error.
Logic wise, there is no infinite loop.
In the utils file, if you comment this const spiedHandleError = jest.spyOn(utils, "handleError"); out, the test will be fine.
Is there a way around this somehow?
I realized it's my own logic that caused the infinite loop. I forgot to add the return statement to each of my if statement.
My spy function now works.
const spiedHandleError = jest.spyOn(utils, "handleError");
await utils.handleError({
err: new StatusError(500, "error"),
emailer: mockEmailer,
});
expect(spiedHandleError).toBeCalledTimes(2);
expect(spiedHandleError.mock.calls).toEqual([
[{ err: new StatusError(500, "error"), emailer: mockEmailer }],
[
{
err: new EmailError("failed to send an error report email."),
emailer: mockEmailer,
},
],
]);
It's impossible to spy or mock a function that is used in the same module it was defined. This is the limitation of JavaScript, a variable cannot be reached from another scope. This is what happens:
let moduleObj = (() => {
let foo = () => 'foo';
let bar = () => foo();
return { foo, bar };
})();
moduleObj.foo = () => 'fake foo';
moduleObj.foo() // 'fake foo'
moduleObj.bar() // 'foo'
The only way a function can be written to allow this defining and consistently using it as a method on some object like CommonJS exports:
exports.handleError = async (...) => {
...
exports.handleError(...);
...
};
This workaround is impractical and incompatible with ES modules. Unless you do that, it's impossible to spy on recursively called function like handleError. There's babel-plugin-rewire hack that allows to do this but it's known to be incompatible with Jest.
A proper testing strategy is to not assert that the function called itself (such assertions may be useful for debugging but nothing more) but assert effects that the recursion causes. In this case this includes console.log calls.
There are no reasons for spyOn to cause infinite loop. With no mock implementation provided, it's just a wrapper around original function. And as explained above, there's no way how it can affect internal handleError calls, so it shouldn't affect the way tested function works.
It's unsafe to spy on utils ES module object because it's read-only by specification and can result in error depending on Jest setup.
I'm testing my GraphQL api using Jest.
I'm using a separate test suit for each query/mutation
I have 2 tests (each one in a separate test suit) where I mock one function (namely, Meteor's callMethod) that is used in mutations.
it('should throw error if email not found', async () => {
callMethod
.mockReturnValue(new Error('User not found [403]'))
.mockName('callMethod');
const query = FORGOT_PASSWORD_MUTATION;
const params = { email: 'user#example.com' };
const result = await simulateQuery({ query, params });
console.log(result);
// test logic
expect(callMethod).toBeCalledWith({}, 'forgotPassword', {
email: 'user#example.com',
});
// test resolvers
});
When I console.log(result) I get
{ data: { forgotPassword: true } }
This behaviour is not what I want because in .mockReturnValue I throw an Error and therefore expect result to have an error object
Before this test, however, another is ran
it('should throw an error if wrong credentials were provided', async () => {
callMethod
.mockReturnValue(new Error('cannot login'))
.mockName('callMethod');
And it works fine, the error is thrown
I guess the problem is that mock doesn't get reset after the test finishes.
In my jest.conf.js I have clearMocks: true
Each test suit is in a separate file, and I mock functions before tests like this:
import simulateQuery from '../../../helpers/simulate-query';
import callMethod from '../../../../imports/api/users/functions/auth/helpers/call-accounts-method';
import LOGIN_WITH_PASSWORD_MUTATION from './mutations/login-with-password';
jest.mock(
'../../../../imports/api/users/functions/auth/helpers/call-accounts-method'
);
describe('loginWithPassword mutation', function() {
...
UPDATE
When I substituted .mockReturnValue with .mockImplementation everything worked out as expected:
callMethod.mockImplementation(() => {
throw new Error('User not found');
});
But that doesn't explain why in another test .mockReturnValue works fine...
Change .mockReturnValue with .mockImplementation:
yourMockInstance.mockImplementation(() => {
throw new Error();
});
in case you want to assert
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
If it's a promise you can also to .rejects www.jestjs.io/docs/en/asynchronous#resolves--rejects
For promises, can use https://jestjs.io/docs/mock-function-api#mockfnmockrejectedvaluevalue
test('async test', async () => {
const asyncMock = jest.fn().mockRejectedValue(new Error('Async error'));
await asyncMock(); // throws "Async error"
});
For testing that error was thrown or not, can use https://eloquentcode.com/expect-a-function-to-throw-an-exception-in-jest
const func = () => {
throw new Error('my error')
}
it('should throw an error', () => {
expect(func).toThrow()
})
For Angular + Jest:
import { throwError } from 'rxjs';
yourMockInstance.mockImplementation(() => {
return throwError(new Error('my error message'));
});
I am getting this error when I am testing my code:
1) Sourcerer Testing: getStatusCode :
Error: Expected undefined to equal 200
I'm not sure why I am getting undefined in my tests but when I run the code I get 200. It might be from not handling promises properly
Test code:
import expect from 'expect';
import rp from 'request-promise';
import Sourcerer from './sourcerer';
describe("Sourcerer Testing: ", () => {
let sourcerer = new Sourcerer(null);
const testCases = {
"https://www.google.com": 200,
// "www.google.com":
};
describe("getStatusCode", () => {
it("", () => {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
expect(sourcerer.getStatusCode()).toEqual(testCases[testCase]);
}
});
});
});
code:
import rp from 'request-promise';
export default class Sourcerer {
constructor(url) {
this.options = {
method: 'GET',
url,
resolveWithFullResponse: true
};
this.payload = {};
}
setSourcererUrl(url) {
this.url = url;
}
getSourcererUrl() {
return this.url;
}
analyzeSourcePage() {
rp(this.options).then((res) => {
console.log(res);
}).catch((err) => {
console.log("ERROR");
throw(err);
});
}
getStatusCode() {
rp(this.options).then((res) => {
console.log(res.statusCode);
return res.statusCode;
}).catch((err) => {
console.log("STATUS CODE ERROR");
return 0;
});
}
}
getStatusCode doesn't return anything. And it should return a promise:
getStatusCode() {
return rp(this.options)...
}
The spec will fail in this case, because it expects promise object to equal 200.
It is even more complicated because the spec is async and there are several promises that should be waited before the spec will be completed. It should be something like
it("", () => {
let promises = [];
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
let statusCodePromise = sourcerer.getStatusCode()
.then((statusCode) => {
expect(sourcerer.url).toEqual(testCase);
expect(statusCode).toEqual(testCases[testCase]);
})
.catch((err) => {
throw err;
});
promises.push(statusCodePromise);
}
return promises;
});
co offers an awesome alternative to Promise.all for flow control:
it("", co.wrap(function* () {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
let statusCode = yield sourcerer.getStatusCode();
expect(statusCode).toEqual(testCases[testCase]);
}
});
Disclaimer: I wouldn't run a for-loop in a single it(), since I want to know which iteration failed. granted that there are ways to achieve that, but that is another story. Also, this very much depends on you test runner, but here is some rules of thumb I find useful.
But for what you have asked, the test should not evaluate until the promise is resolved. sometimes (e.g. in mocha), that means returning the promise from the it() internal function. sometimes, it means getting a done function and calling it when you are ready for the test to evaluate. If you provide more info on your test framework, I may be able to help (others certainly would be)