New to Jest, trying to write a basic test case to check the Dropdown feature in Jest.
I have tried the below code:
test( "update state with dropdown", async () => {
let wrapper = mount( <MemoryRouter initialEntries={[ “/workflow/new" ]}><Workflow/></MemoryRouter> );
expect.assertions( 7 );
// Mock validation API calls
let apiWorkflowPromise = Promise.resolve( apiWorkflowResult );
get.mockImplementation( () => apiWorkflowPromise );
// ACT
let instance = wrapper.find( “Workflow” ).instance();
instance.handleWorkflowDropDownChange( { target: { value: “NewZealand” } } );
await expect( apiWorkflowPromise ).resolves.toBeDefined();
// ASSERT
let selectedDiagnosticWorkflow = instance.state.selectedWorkflow;
let supportedParameters = instance.state.workflow.supportedParameters;
expect( selectedWorkflow ).toEqual( “NewZealand” );
} );
Getting the below error:
ReferenceError: event is not defined
at Workflow.handleWorkflowDropDownChange
Related
I'm trying to test a service that has a listener of the a custom Event Emitter in node with typescript and mocha, sinon.
My custom emmiter;
class PublishEmitter extends EventEmitter {
publish(id: string) {
this.emit('publish', id);
}
}
My service use case:
export default class PublishVehicle {
constructor(
private findVehicle: FindVehicle, // Service that contains find methods on repository
private updateVehicle: UpdateVehicle, // Service that contains update methods on repository
private logger: ILogger,
) {
this.producer = producer;
this.logger = logger;
}
listen() {
this.logger.log('debug', 'Creating listener on PublishEmitter');
this.publishListener = this.publishListener.bind(this);
pubsub.on('publish', this.publishListener);
}
/**
* Listener on PublishEmitter.
*
* #param event
*/
async publishListener(event: string) {
try {
const vehicle = await this.findVehicle.findById(event);
if (vehicle?.state === State.PENDING_PUBLISH) {
//
const input = { state: State.PUBLISH };
await this.updateVehicle.update(vehicle.id, input);
this.logger.log('debug', `Message sent at ${Date.now() - now} ms`);
}
this.logger.log('debug', `End Vehicle's Publish Event: ${event}`);
} catch (error) {
this.logger.log('error', {
message: `publishListener: ${event}`,
stackTrace: error,
});
}
}
}
and in my test file:
import chai from 'chai';
const { expect } = chai;
import sinon from 'sinon';
import { StubbedInstance, stubInterface } from 'ts-sinon';
import pubsub from './PublishEmitter';
describe('Use Case - Publish Vehicle', function () {
let mockRepository: MockVehicleRepository;
let publishVehicle: PublishVehicle;
let findVehicleUseCase: FindVehicle;
let updateVehicleUseCase: UpdateVehicle;
before(() => {
const logger = Logger.getInstance();
mockRepository = new MockVehicleRepository();
findVehicleUseCase = new FindVehicle(mockRepository, logger);
updateVehicleUseCase = new UpdateVehicle(mockRepository);
publishVehicle = new PublishVehicle(
findVehicleUseCase,
updateVehicleUseCase,
logger,
);
});
afterEach(() => {
// Restore the default sandbox here
sinon.restore();
});
it('Should emit event to publish vehicle', async () => {
const vehicle = { ... }; // dummy data
const stubFindById = sinon
.stub(mockRepository, 'findById')
.returns(Promise.resolve(vehicle));
const stubUpdate = sinon
.stub(mockRepository, 'update')
.returns(Promise.resolve(vehicle));
const spy = sinon.spy(publishVehicle, 'publishListener');
publishVehicle.listen();
pubsub.publish(vehicle.id);
expect(spy.calledOnce).to.be.true; // OK
expect(stubFindById.calledOnce).to.be.true; // Error (0 call)
expect(stubUpdate.calledOnce).to.be.true; // Error (0 call)
});
});
When I debug this test, indeed the methods are called but they seem to be executed after it has gone through the last expect lines.
The output:
1 failing
1) Use Case - Publish Vehicle
Should emit event to publish vehicle:
AssertionError: expected false to be true
+ expected - actual
-false
+true
UPDATE
Finally I was be able to solve my problem wrapping expect lines in setTimeout.
setTimeout(() => {
expect(spy.calledOnce).to.be.true; // OK
expect(stubFindById.calledOnce).to.be.true; // OK
expect(stubUpdate.calledOnce).to.be.true; // OK
done();
}, 0);
I built a simple button component using Stencil and assigned 4 events (onMouseDown, onMouseUp onMouseEnter, onMouseLeave), to the button. The component looks like this:
.
.
.
#State() buttonState: string ='disabled';
.
.
.
someInternalLogic(eventName: Events) {
...//just sets a state variable of this.buttonState
}
render() {
return (
<button
onMouseDown={() => this.someInternalLogic(Events.MOUSEDOWN)}
onMouseUp={() => this.someInternalLogic(Events.MOUSEUP)}
onMouseEnter={() => this.someInternalLogic(Events.MOUSEENTER)}
onMouseLeave={() => this.someInternalLogic(Events.MOUSELEAVE)}
>
</button>
);
}
I'm new to testing in general and Jest in particular. I'm having troubles understanding how to test these events synthetically. I've come up with a workaround which works, but is obviously not the way to go.
The workaround:
it('should mouseleave', async () => {
const button = await page.root.shadowRoot.querySelector('button');
const mouseleave = new window.Event("mouseleave", {
bubbles: false,
cancelable: false
});
let mouseleaveBool = false;
button.addEventListener("mouseleave", e=>{
mouseleaveBool = true;
});
await button.dispatchEvent(mouseleave);
await page.waitForChanges();
expect(mouseleaveBool ).toBeTruthy();
});
Instead of dispatching events you can directly call event handlers on your component instance
So for this component
export class TestBtn {
onMouseLeave() {
// do something
}
render() {
return (
<Host>
<button onMouseLeave={() => this.onMouseLeave()}>Test</button>
</Host>
);
}
}
Test can look like this
describe('test-btn', () => {
it('does something on mouse leave', async () => {
// arrange
const page = await newSpecPage({
components: [TestBtn],
html: `<test-btn></test-btn>`,
});
let component = page.rootInstance as TestBtn;
// act
component.onMouseLeave();
// assert
// check if did something
});
});
We want to fail the build if more console errors are introduced. For example, let's say console.error was called 30 times in the whole test suite. If another error is introduced this will increase to 31, which we don't want. Is there a way to prevent this?
For one test suite it is possible with:
const spy = jest.spyOn(console, "error");
let count = 0;
afterEach(() => {
count += spy.mock.calls.length;
});
afterAll(() => {
if (count > 2) {
throw Error(`oops error count: ${count}`);
}
});
but it would be nice to have this globally defined.
We solved this in a slightly different way:
// src/utils/testUtils
let consoleErrorSpy;
export const spyOnConsoleError = () => {
consoleErrorSpy = jest.spyOn(console, "error");
};
/**
* We are using this to prevent the console errors from increasing.
* These are our preferences in order of priority:
* 1. Don't call this method
* 2. Call this method at the end of a specific test (eg. for an error that can't be solved)
* 3. Call this method in `afterEach` (eg. for an async error that can't be solved)
*/
export const catchConsoleErrors = ({ silenced = [] } = {}) => {
const alwaysSilencedErrors = [
'<bug from a 3rd party library>'
];
const forbiddenCalls = [];
const silencedCalls = [];
for (const call of consoleErrorSpy.mock.calls) {
if (
new RegExp([...alwaysSilencedErrors, ...silenced].join("|")).test(call)
) {
silencedCalls.push(call);
} else {
forbiddenCalls.push(call);
}
}
for (const silencedCall of silencedCalls) {
// eslint-disable-next-line no-console
console.log("SILENCED\n---\n" + silencedCall.join(",") + "\n---");
}
expect(forbiddenCalls).toHaveLength(0);
// We clear the mock here so nothing happens if the method is called again for the same test,
// which is the case when this method is called in a specific test (file)
// as it is also called in `afterEach` in setUpTests.js
consoleErrorSpy.mockClear();
};
// some test file
afterEach(() => {
catchConsoleErrors({
silenced: [
"Warning: Can't perform a React state update on an unmounted component.*"
]
});
});
// src/setupTests.js
spyOnConsoleError();
afterEach(() => {
catchConsoleErrors();
});
const jsdom = require( 'jsdom' );
const JSDOM = jsdom.JSDOM;
const Utils = require( '../helper/utils.js' );
module.exports = {
/**
* Controller method to render a HTML string.
*
* #param {Object} request Object that holds http request information.
* #param {Object} response Object that holds http response functions.
*/
render: ( request, response ) => {
const resourceLoader = new jsdom.ResourceLoader( { strictSSL: false } );
const virtualConsole = new jsdom.VirtualConsole();
// Define JSDOM configurations.
const options = {
contentType: 'text/html',
referrer: request.body.originUrl,
resources: resourceLoader,
runScripts: 'dangerously',
url: ( request.body.originUrl + request.body.relativeUrl ),
// virtualConsole: virtualConsole.sendTo( console ),
beforeParse( window ) {
// Sync all browser related global properties with Node global properties.
Utils.syncGlobalProperties( window );
// Add event listener to know when page is fully loaded.
window.addEventListener( 'load', async function ( e ) {
// Set SSR Flag to indicate the page is Server-Side rendered.
Utils.setSSRFlag();
const renderedHtml = dom.serialize(); // eslint-disable-line no-use-before-define
dom.window.close(); // eslint-disable-line no-use-before-define
dom = null; // eslint-disable-line no-use-before-define
global.window = null;
global.document = null;
// if ( process.memoryUsage().heapUsed > 200000000 ) { // memory use is above 200MB
// global.gc();
// await new Promise( resolve => setTimeout( resolve, 500 ) );
// }
// setInterval( function () {
// //only call if memory use is bove 200MB
// if ( process.memoryUsage().heapUsed > 200000000 ) {
// global.gc();
// }
// }, 10000 );
//Return response after page load.
response.send( renderedHtml ); // eslint-disable-line no-use-before-define
} )
},
};
// Render HTML and Javascript using JSDOM.
let dom = new JSDOM( request.body.html, options );
},
}
//syncGlobalproperties function
syncGlobalProperties: window => {
global.window = window;
global.document = window.document;
// Declare some matchMedia attributes and functions to avoid undefined error.
global.window.matchMedia = global.window.matchMedia || function () {
return {
matches: false,
addListener: function () {},
removeListener: function () {},
};
};
//Declared scrollTo function to avoid undefined error.
global.window.scrollTo = () => {};
//Declared requestAnimationFrame function to avoid undefined error.
global.window.requestAnimationFrame = () => {};
// Assign common properties to node global variable.
Object.keys( window ).forEach( key => {
if ( ! ( key in global ) ) {
global[key] = window[key];
}
} );
},
I'm trying render react code through ssr using jsdom, but the heap memory keeps increasing till 1.5gb and then crashes giving console message as "JavaScript heap out of memory". I tried mannualy injesting the garbage collector but that also doesn't seem to work.
Tried a couple of things:
1. dom.window.close();
dom = null;
global.window = null;
global.document = null;
2. if ( process.memoryUsage().heapUsed > 200000000 ) {
// memory use is above 200MB
global.gc();
await new Promise( resolve => setTimeout( resolve, 500 ) );
}
3. setInterval( function () {
//only call if memory use is bove 200MB
if ( process.memoryUsage().heapUsed > 200000000 ) {
global.gc();
}
}, 10000 );
None of the above seems to be working.
I have the following js function:
const modelUtils = {
modelingObj(obj, stringVal = ‘RE’) {
let newObj;
//my logic for setting value of newObj
return newObj;
};
export default modelUtils;
I want to test and see that based on a specific params I get a particular result, the issue is I’m always returning back an empty object.
Test.js
import modelUtils from '../modelUtils';
jest.unmock('../modelUtils');
describe(' testing modelUtils', () => {
let test;
const mockData = {
myProperty: [],
};
describe('testing modelingObj function', () => {
it('For my first test’, () => {
test = modelUtils.mockData, ‘TR’);
expect(test).toEqual({ myProperty: [] });
});
});
});