I want to handle uncaught exceptions occurs while running cypress tests using cucumber.I have to apply the settings before all the feature tests in all modules. How can I handle that?
I tried this way, cy.on function in cypress config, but it is showing error for me.
async setupNodeEvents(on, config) {
await addCucumberPreprocessorPlugin(on, config);
on(
'file:preprocessor',
createBundler({
plugins: [createEsbuildPlugin(config)],
}),
);
cy.on('uncaught:exception', (err, runnable) => {
return false
})
return config;
},
I would suggest to use the code in a different place, either in cypress/support/e2e.js
// cypress/support/e2e.js
Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
or inside the step file
// cypress/e2e/step-definition-for-feature-x.ts
import { When, Then } from "#badeball/cypress-cucumber-preprocessor";
Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
When('tests while ignoring an uncaught exception', () => {
...
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'));
});
File: index.ts // Class Based Default exported
getItemsA() {
return Promise.resolve({ // Api Call in real scenario. Mocking here for now.
success: true;
result: [{
itemA: []
}]
});
}
getItemsB() {
return Promise.resolve({ // Api Call in real scenario. Mocking here for now.
success: true;
result: [{
itemB: []
}]
});
}
File service.ts
import { getItemsA, getItemsB } from 'index.ts';
getService() {
return new Promise(async (resolve, reject) => {
const parallelApis = await Promise.all([ // UT Stucks here. Not able to get the parallelApis response.
getItemsA(), getItemsB()
]);
.... other mapping
resolve({
.......
});
});
}
File test.ts
import items from 'index.ts'; // Class based. Default exported
import service from 'service.ts'; // Class based. Default exported
import { expect } from "chai";
import * as sinon from "sinon";
describe.only("Service", () => {
let itemAStub;
let itemBStub;
beforeEach(() => {
itemAStub = sinon.stub(items, "getItemsA");
itemBStub = sinon.stub(items, "getItemsB");
itemAStub.callsFake(() => {
return Promise.resolve({
body: myMock // Dummy
});
});
itemBStub.callsFake(() => {
return Promise.resolve({
body: someOtherMock // Dummy
});
});
});
afterEach(() => {
itemAStub.restore();
itemBStub.restore();
});
it("Should filter valid gift options", (done) => {
service.getService().then(res => {
console.log("RESPONSEEEEEE", res);
expect(res).to.deep.equal(myMockResponse);
done();
})
.catch(err => {
console.log("Errorr");
done(err);
});
});
});
Can somebody help me to identify the issue when i tried to run the test case I am getting the below error.
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I know that extending the timeout is not a solution to fix the issue. Stub is created, but not sure why it is getting timeout error when code reaches await Promise.all. If I remove done() then test case will succeed but then function is not getting executed. Any help would be really appreciated.
When we run Mocha from the command line, it will read this file which has a default timeout.
To change this time you can reset using:
./node_modules/.bin/mocha --timeout 20000
I have a try/catch block in my functions that I want to test. Both functions call the same execute function and will catch if it throws an error. My tests are setup where I'm mocking the module near the top, and I can then verify how many times that jest function is called.
What I can't seem to figure out is how to force execute to throw an error on the second test, and then go back to the default mock implementation. I've tried re-assigning jest.mock in the individual test but it doesn't seem to work.
import {execute} from '../src/execute'
jest.mock('../src/execute', () => ({
execute: jest.fn()
}))
describe('git', () => {
afterEach(() => {
Object.assign(action, JSON.parse(originalAction))
})
describe('init', () => {
it('should stash changes if preserve is true', async () => {
Object.assign(action, {
silent: false,
accessToken: '123',
branch: 'branch',
folder: '.',
preserve: true,
isTest: true,
pusher: {
name: 'asd',
email: 'as#cat'
}
})
await init(action)
expect(execute).toBeCalledTimes(7)
})
})
describe('generateBranch', () => {
it('should execute six commands', async () => {
jest.mock('../src/execute', () => ({
execute: jest.fn().mockImplementation(() => {
throw new Error('throwing here so. I can ensure the error parsed properly');
});
}))
Object.assign(action, {
silent: false,
accessToken: '123',
branch: 'branch',
folder: '.',
pusher: {
name: 'asd',
email: 'as#cat'
}
})
// With how this is setup this should fail but its passing as execute is not throwing an error
await generateBranch(action)
expect(execute).toBeCalledTimes(6)
})
})
})
Any help would be appreciated!
jest.mock in should execute six commands doesn't affect ../src/execute module because it has already been imported at top level.
jest.mock at top level already mocks execute with Jest spy. It's preferable to use Once implementation to not affect other tests:
it('should execute six commands', async () => {
execute.mockImplementationOnce(() => {
throw new Error('throwing here so. I can ensure the error parsed properly');
});
...
Also the mock should be forced to be ES module because execute is named import:
jest.mock('../src/execute', () => ({
__esModule: true,
execute: jest.fn()
}))
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 creating a jest test to test if metrics were logged for the error handling of the superFetch function. My approach is creating a mock function for retryFetch and returning a Promise reject event. I expect that to go to the superFetch catch but it keeps ending up in superFetch then. What can I do to handle my errors in superFetch catch?
These are the functions:
// file: fetches.js
export function retryFetch(url) {
return new Promise((resolve, reject) => {
fetch(url).then(response => {
if (response.ok) {
resolve(response);
return;
}
throw new Error();
}).catch(error => {
createSomething(error).then(createSomething => {
reject(createSomething);
});
return;
});
});
});
export function superFetch(url, name, page) {
return retryFetch(url)
.then(response => {
return response;
}).catch(error => {
Metrics.logErrorMetric(name, page);
throw error;
});
}
My jest test:
import * as fetch from '../../src/utils/fetches';
describe('Fetch fails', () => {
beforeEach(() => {
fetch.retryFetch = jest.fn(() => Promise.reject(new Error('Error')));
});
it('error metric is logged', () => {
return fetch.superFetch('url', 'metric', 'page').then(data => {
expect(data).toEqual(null);
// received data is {"ok": true};
// why is it even going here? im expecting it to go skip this and go to catch
}).catch(error => {
// this is completely skipped. but I'm expecting this to catch an error
// received error is null, metric was not called
expect(Metrics.logErrorMetric).toHaveBeenCalled();
expect(error).toEqual('Error');
});
});
});
The problem is that you overwrite the function in the exported module but superFetch use the original one inside of the module, so the overwrite will have no effect.
You could mock fetch directly like this:
global.fetch = jest.mock(()=> Promise.reject())