I have a static method that creates a custom event and dispatches it:
class MyComponent extends {
static refreshComponent() {
const event = new Event('refreshComponent');
document.dispatchEvent(event);
}
render() {
MyComponent.refreshComponent();
}
}
I am trying test as below:
describe('refreshComponent', () => {
it('should dispatch an event', () => {
const document = { dispatchEvent: jest.fn() };
wrapper.refreshGoalsComponent();
expect(document.dispatchEvent).toHaveBeenCalledWith('refreshComponent');
});
});
But the dispatchEvent is not called here, as there is no mock for 'new Event()'. Is there a way to mock it? Please help
It's a little clunky looking, but something like this should work if you want to verify an event with the expected type was dispatched:
describe('refreshComponent', () => {
it('should dispatch an event', () => {
const dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
// Trigger component render here...
expect(dispatchEventSpy).toHaveBeenCalledWith(expect.any(Event));
expect(dispatchEventSpy.mock.calls[0][0].type).toBe('refreshComponent');
});
});
You can test dispatch event like this:
describe('refreshComponent', () => {
it('should dispatch an event', () => {
jest.spyOn(global, 'Event').mockImplementation((type: string, eventInit?: any) => ({ type, eventInit }));
const mockDispatchEvent = jest.spyOn(document, 'dispatchEvent').mockImplementation(() => true);
// render your component
expect(mockDispatchEvent).toHaveBeenCalledWith({
type: 'refreshComponent',
});
});
});
This way can expect event type and event init value. If you don't want to expect detail event, we does not need to mock Event and expect like:
expect(mockDispatchEvent).toHaveBeenCalledWith(expect.any(Event));
You can mock globals with Jest:
describe('your test', () => {
let EventBak
let documentBak
beforeAll(() => {
EventBak = global.Event
documentBak = global.document
global.Event = jest.fn()
global.document = {
...global.document,
dispatchEvent: jest.fn()
}
})
afterAll(() => {
global.Event = EventBak
global.document = documentBak
})
it('...', () => {
...
expect(global.document.dispatchEvent).toHaveBeenCalled()
})
})
Related
Component:
const MyComp = ({ handler }) => {
return <button onClick={handler}>Test</button>
};
Test:
it('Calls the handler', async () => {
const handler = jest.fn();
render(<MyComp handler={handler} />);
const button = screen.getByRole('button', { name: /Test/i });
await fireEvent(button, new MouseEvent('click'));
expect(handler).toHaveBeenCalled();
});
Expected number of calls: >= 1
Received number of calls: 0
Three options:
Make the event bubble, as shown in the example (it doesn't return a promise, no await needed):
fireEvent(button, new MouseEvent("click", { bubbles: true }));
Use the convenience method, which adds default event properties including bubbling (likewise):
fireEvent.click(button);
Use userEvent (does return a promise, as of v14):
await userEvent.click(button);
Try using userEvent.click, like this
it('Calls the handler', async () => {
const handler = jest.fn();
render(<MyComp handler={handler} />);
const button = screen.getByRole('button', { name: /Test/i });
await userEvent.click(button);
expect(handler).toHaveBeenCalled();
});
im using an http request function as the handler function in middy and then use the ssm middleware to fetch some ssm parameters before initiating the http request.
like this:
const makeThirdPartyServiceRequest = middy(async ({ params }) => {
logger.info(`SENDING Request to ${endpoint} API`)
const url = `https://someurltoathirdpartyservice`
const options = {
method: 'POST',
body: params
}
return helpers.makeRequest(url, options)
})
makeThirdPartyServiceRequest.use(ssm(......))
However in my jest unit test Im trying to mock makeThirdPartyServiceRequest and explicitly say it should resolve to a value:
jest.mock('../src/thirdPartyService', () => ({
__esModule: true,
default: {
...(jest.requireActual('../src/thirdPartyService') as { default: {} }).default,
makeThirdPartyServiceRequest: jest.fn()
}
}))
export {}
import thirdPartyService from '../src/thirdPartyService'
And then in the test i say:
describe('makeThirdPartyServiceRequest()', () => {
it('should makeThirdPartyServiceRequest', async () => {
// Given
// })
const mockedThirdPartyServiceRequest = mocked(thirdPartyService.makeThirdPartyServiceRequest).mockResolvedValue({})
// When
const result = await thirdPartyService.makeThirdPartyServiceRequest(something)
// Then
expect(mockedThirdPartyServiceRequest).toHaveBeenCalledTimes(1)
expect(mockedThirdPartyServiceRequest.mock.calls[0][0].params.toString()).toBe(expectedParams)
})
})
However for some reason the middy middleware is still being invoked, which i clearly dont want and i have tried to mock away... what am i doing wrong?
You need to mock middy instead, to make it becomes a useless function. That function recipe a function as a parameter and return that parameter.
import thirdPartyService from '../src/thirdPartyService'
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockReturnValue(handler), // ...use(ssm()) will return handler function
}
}
})
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
hoangdv answer is also valid, but i will answer as well how i continued.
if you completely want to mock middy you mock like following:
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockImplementation(() => {
// ...use(ssm()) will return handler function
return {
before: jest.fn().mockReturnValue(handler)
}
})
}
}
})
However if you dont want to completely mock middy, you can instead mock the async getInternal function from middy/util called in before like this:
jest.doMock('#middy/util', () => ({
...(jest.requireActual('#middy/util') as {}),
getInternal: jest.fn()
}))
import { getInternal } from '#middy/util'
and then in the test
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(getInternal).mockResolvedValue({
twilioSecrets: { accountSid: 'someSID', serviceId:
'someServiceID', token: 'someToken' }
})
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
this will mock the async part of middy.
Not working function call with 'click' trigger in test.
I called function - it is working
I triggered click but test was failed.
describe("Message.test.js", () => {
let wrapper;
const createWrapper = propsData => mount(Message, { propsData
describe("Events", () => {
beforeEach(() => {
wrapper = createWrapper({ message: "Cat" });
});
//Working
it("calls handleClick", () => {
const spy = jest.spyOn(wrapper.vm, 'handleClick');
wrapper.vm.handleClick();
expect(spy).toHaveBeenCalled();
});
//NOT WORKING. WHY?
it("calls handleClick when click on message", () => {
wrapper.vm.handleClick = jest.fn();
//It is Ok
expect(wrapper.contains('.message')).toBe(true);
// #click="handleClick" on element
wrapper.find('.message').trigger('click');
expect(wrapper.vm.handleClick).toHaveBeenCalledTimes(1);
})
});
I added console.log to the function. During the test, I see what function was called.
it('calls handleClick when click on message', () => {
const handleClick = jest.fn()
wrapper.setMethods({ handleClick })
const el = wrapper.find('.message').trigger('click')
expect(handleClick).toBeCalled()
})
// stub
it('triggers a message-clicked event when a handleClick method is called', () => {
const stub = jest.fn()
wrapper.vm.$on('message-clicked', stub)
wrapper.vm.handleClick()
expect(stub).toBeCalledWith('Cat')
})
zoomOut(callback) {
// Zooms out the current screen
this.view.current.zoomOut(300).done(() => {
(hasCallback(callback)) && callback();
});
}
I'm trying to test the function above but I keep getting the following error:
TypeError: this.view.current.zoomOut(...).done is not a function
How can I mock this method chain in Jest?
Thanks to BudgieInWA, I was able to solve this problem by returning done.
For those who are testing a React component with Enzyme, here's how you can do it:
it('should call callback', () => {
const wrapper = shallow(<Zoom {...minProps}/>);
const instance = wrapper.instance();
const callback = jest.fn();
instance.view = {
current: {
zoomOut: jest.fn(() => {
return {
done: jest.fn((callback) => {
callback();
})
};
})
}
};
expect(callback).toHaveBeenCalledTimes(0);
instance.zoomOut(callback);
expect(callback).toHaveBeenCalledTimes(1);
});
You could try this:
const mockZoomOut = jest.fn(() => ({ done(cb) { cb(); } }));
const mockThis = {
view: {
current: {
zoomOut: mockZoomOut,
},
},
};
test('it does', () => {
const cb = jest.fn();
zoomOut.apply(mockThis, [cb]);
expect(mockZoomOut).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledTimes(1);
});
See Jest Mock Functions and fn.apply.
If you are testing the behaviour of the class as a whole, then you could set up the instance that you are testing to have this.view.current.zoomOut be mockZoomOut somehow.
I would like to spy a function to test if this function is called in a catch block when a promise is rejected. My code is a react component like this
export class ResetPassword extends Component {
handleSubmit = e => {
e.preventDefault();
this.props
.resetPassword()
.then(() => {
this.props.history.push(LOGIN_PATH);
})
.catch(({ error }) => {
this.props.displayErrorAlert('impossible to change password. You should ask for a new reset password link',6000);
});
};
}
Here I want to test if the function displayErrorAlert has been called. I made this test
it('validate form', () => {
const resetPassword = () => {
return Promise.reject({
error: {
response: {
data: {
errors: [
{
title: 'foo',
},
],
},
},
},
});
};
const displaySpy = sinon.spy();
const wrapper = mount(
<ResetPassword
history={{}}
resetPassword={resetPassword}
displayErrorAlert={displaySpy}
/>
);
wrapper.instance().handleSubmit({
preventDefault: () => {},
});
expect(displaySpy.calledOnce).toEqual(true);
});
The spy is called but asynchronously of course so my test always fails. I would like to find a way to test if the function has been called only once the catch block has been called and I have no idea how to do that.
Sinon provides you everything you need when handling promises, you can resolve and reject a stubbed promise using sinon.stub().
const resetPassword = sinon.stub();
const displayErrorAlert = sinon.spy();
const preventDefault = sinon.spy();
const props = {
resetPassword,
displayErrorAlert,
history: []
};
describe('Given a component', () => {
let component;
describe('when rendered', () => {
beforeAll(() => {
component = shallow(<ResetPassword {...props} />);
});
describe('and the form is submitted and there is an error reseting the password', () => {
beforeAll(() => {
resetPassword.rejects(new Error('Oops!'));
component.find('button').simulate('click', { preventDefault });
});
it('should invoke the displayErrorAlert function', () => {
expect(displayErrorAlert.calledOnce).toBeTruthy();
});
});
});
});
I found an other solution, I return the promise in the handleSubmit function and use it in my tests.
export class ResetPassword extends Component {
handleSubmit = e => {
e.preventDefault();
return this.props
.resetPassword()
.then(() => {
this.props.history.push(LOGIN_PATH);
})
.catch(({ error }) => {
this.props.displayErrorAlert('impossible to change password. You should ask for a new reset password link',6000);
});
};
}
and my test
it('validate form', () => {
const resetPassword = () => {
return Promise.reject({
error: {
response: {
data: {
errors: [
{
title: 'foo',
},
],
},
},
},
});
};
const displaySpy = sinon.spy();
const wrapper = mount(
<ResetPassword
history={{}}
resetPassword={resetPassword}
displayErrorAlert={displaySpy}
/>
);
expect.assertions(1);
const promise = wrapper.instance().handleSubmit({
preventDefault: () => {},
});
return promise.then(() => {
expect(displaySpy.calledOnce).toEqual(true);
});
});