I am using Stenciljs and Jest.
I am having a hard time testing events triggered in the lifecycle (componentDidLoad).
I am able to enter the lifecycle, but unable to test the .emit() event.
I have tried the following:
.spec.ts
it('should spyOn componentDidLoad', () => {
const component = new App();
jest.spyOn(component, 'componentDidLoad');
let eventSpy = jest.fn();
component.document.addEventListener('toScroll', eventSpy);
expect(component.componentDidLoad()).toHaveBeenCalled();
});
Here is the situation at a glance:
.tsx
#Event() toScroll: EventEmitter<void>;
componentDidLoad() {
this.toScroll.emit();
}
.spec.ts
it('should spyOn componentDidLoad', () => {
const component = new App();
jest.spyOn(component, 'componentDidLoad');
// need a way to test the emit() here.
expect(component.componentDidLoad()).toHaveBeenCalled();
});
The (logical) following error occurs:
● render component › should spyOn componentDidLoad
TypeError: Cannot read property 'emit' of undefined
Since Stencil is instantiating your EventEmitter I would recommend using end-to-end testing using newE2EPage:
import { newE2EPage } from '#stencil/core/testing';
it('should emit an event on load', async () => {
const page = await newE2EPage();
await page.setContent(`
<my-app></my-app>
`);
const toScroll = await page.spyOnEvent('toScroll');
await page.waitForChanges();
expect(toScroll).toHaveReceivedEvent();
});
Related
I am using jest & react-testing-library.
In my component on didMount I add an event listener to the window reacting on resize events. Based on resize events, another function is called. In my test, that function is mocked.
Now I have the problem, that I am not able to trigger these resize events.
Is there any way to get that done?
window.resizeTo(500, 500);
fireEvent.resize(window);
fireEvent(window, new Event("resize"));
I tried to achieve the triggering of the event listener on different ways, but nothing worked.
Thanks for your help in advance :)
Here's an example of how to spy on window.addEventListener to make sure that it's been invoked (and your mock function is registered) before you dispatch the resize event:
The example component is a functional one and uses the effect hook, but the test should be the same for a class component.
TS Playground
example.test.tsx:
import {useEffect} from 'react';
import {fireEvent, render, waitFor} from '#testing-library/react';
type ComponentProps = { callback: () => unknown };
function Component ({callback}: ComponentProps) {
useEffect(() => {
const handleResize = () => {
// You described some extra logic here,
// but this is where the callback is invoked:
callback();
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [callback]);
return null;
}
test('callback is invoked after window resize event', async () => {
let aResizeEventListenerWasAddedToWindow = false;
const originalMethod = window.addEventListener;
const spy = jest.spyOn(window, 'addEventListener');
spy.mockImplementation((...args) => {
// Because this custom implementation is created for the spy,
// Jest will no longer automatically invoke the original.
// It needs to be done manually:
originalMethod(...args);
const [eventType] = args;
if (eventType === 'resize') aResizeEventListenerWasAddedToWindow = true;
});
const callback = jest.fn();
render(<Component {...{callback}} />);
// Wait for the resize handler in the component to be registered (useEffect callback is async)
await waitFor(() => expect(aResizeEventListenerWasAddedToWindow).toBeTruthy());
fireEvent.resize(window);
expect(callback).toHaveBeenCalled();
// Restore the original method to window.addEventListener
spy.mockRestore();
});
I'm trying to mock the return value of messages.create() method from twilio-node library.
Since the create method resides inside the interface called messages, i can't directly mock the return value of create method.
My Unit test:
import {
createStubInstance,
StubbedInstanceWithSinonAccessor,
} from '#loopback/testlab';
import sinon from 'sinon';
import {Twilio} from '../../../../clients/whatsapp-sms-clients/twilio.whatsapp-sms-clients';
import twilio from 'twilio';
describe('Twilio client (UnitTest)', () => {
let twilioMock: StubbedInstanceWithSinonAccessor<twilio.Twilio>;
let logger: StubbedInstanceWithSinonAccessor<LoggingService>;
let twilioClient: Twilio;
beforeEach(() => {
twilioMock = createStubInstance(twilio.Twilio);
logger = createStubInstance(LoggingService);
twilioClient = new Twilio(twilioMock, logger);
});
it('should create the message', async () => {
twilioMock.stubs.messages.create.resolves({
// mocked value
});
});
});
Thanks in advance.
Twilio developer evangelist here.
I've not worked with testlab/sinon like this before, but I think I have an idea of what you need to do, if not the right syntax.
You'd need to stub the response to twilioMock.messages to return an object that has a create property that is a stubbed function that resolves to the result you want. Something like this might work, or at least set you on the right track:
it('should create the message', async () => {
// Create stub for response to create method:
const createStub = sinon.stub().resolves({
// mocked value
});
// Stub the value "messages" to return an object that has a create property with the above stub:
twilioMock.stubs.messages.value({
create: createStub
});
// Rest of the test script
});
Edit
OK, using value above didn't work. I tried again. This version strips out your custom Twilio wrapper from the example and just calls things directly on the Twilio client stub itself. Hopefully you can use this as inspiration to work it into your tests.
What I realised is that twilioClient.messages is a getter and is dynamically defined. So, I directly stubbed the result on the stub client.
import {
createStubInstance,
StubbedInstanceWithSinonAccessor,
} from "#loopback/testlab";
import sinon from "sinon";
import { Twilio } from "twilio";
describe("Twilio client (UnitTest)", () => {
let twilioMock: StubbedInstanceWithSinonAccessor<Twilio>;
beforeEach(() => {
twilioMock = createStubInstance(Twilio);
});
it("should create the message", async () => {
const createStub = sinon.stub().resolves({
sid: "SM1234567",
});
sinon.stub(twilioMock, "messages").get(() => ({
create: createStub,
}));
const message = await twilioMock.messages.create({
to: "blah",
from: "blah",
body: "hello",
});
expect(message.sid).toEqual("SM1234567");
});
});
The above test passes for me in my setup.
Testing function
import fetch from 'node-fetch';
class Services () {
someFunc() {
const data = await fetch(dummy url);
// business logic
}
anotherFunc() {
const data = await fetch(dummy url);
// business logic
}
}
export default new Service();
Actual testing
import fetch from 'node-fetch';
jest.mock('node-fetch', () => jest.fn());
beforeEach(() => {
mocked(fetch).mockClear();
})
describe('test first method', () => {
it('should fetch some data without mock fetch', async () => {
const data = await service.someFunc();
expect(data).toHaveProperty('items');
})
})
describe('test second method', () => {
it('should return data with mock fetch', async () => {
mocked(fetch).mockImplementationOnce(() => Promise.reject());
const data = await service.anotherFunc();
expect(data).toHaveProperty('items');
})
})
At this moment I have a problem with the fetch functions in the first describe case and get " TypeError: Cannot read property 'then' of undefined". So I need a mocked version of fetch only in the second describe case. In the first describe case it should use fetch without mock function replacement.
How I can fix it? Will be grateful for the assist.
Basically, I want to make sure the methods of analytics are called with certain properties but so far it is not working:
Cannot spy the logAppOpen property because it is not a function; undefined given instead
the library is successfully mocked since I can see console log out of my jest.fn():
jest.mock('#react-native-firebase/analytics', () => {
return () => ({
logAppOpen: jest.fn(() => console.log('mocked fun called')), //===>shown correctly
})
})
My class is:
import analytics from '#react-native-firebase/analytics';
export default class GA {
appStarted = async () =>{
console.log('appStarted called'); //==> showing
await analytics().logAppOpen();
}
}
my test:
it("should log app starting", async () =>{
const spy = jest.spyOn(analytics, 'logAppOpen') //===>FAILS HERE
congst ga = new GA();
await ga.appStarted();
expect(spy).toHaveBeenCalled();
})
but in my test: console.log(analytics) does show an empty object {}
It's analytics().logAppOpen() while jest.spyOn tries to spy on analytics.logAppOpen which doesn't exist.
For lazily evaluated spied functions it's easier to expose them as variables:
const mockLogAppOpen = jest.fn();
jest.mock('#react-native-firebase/analytics', () => {
return jest.fn()
.mockReturnValue({ logAppOpen: mockLogAppOpen });
});
This way it can be accessed for call assertions. There's no need for jest.spyOn for a function that is already a spy.
I've got a very basic component that uses axios.all to make 3 calls to a jokes api and then stores the values in state. I then map those values on the page. Very basic stuff.
I'm trying to write a test that mocks the axios.all that I can pass some hard coded responses to. I want to prove that the data binding is happening correctly after the call has resolved.
I'm having a very hard time doing this, and I was wondering if anyone had any insights.
Link to CodeSandbox
Thanks in advance for any and all help!
The problem with your test is that you are not mocking the axios methods. Simply calling it axiosMock when you import the library is not how it works.
You have to actually mock the methods you use:
describe("<TestScreen />", () => {
afterEach(() => {
cleanup();
jest.clearAllMocks();
});
beforeEach(() => {
// Mock all the calls to axios.get, each one returning a response.
axios.get = jest
.fn()
.mockResolvedValueOnce(RESPONSES[0])
.mockResolvedValueOnce(RESPONSES[1])
.mockResolvedValueOnce(RESPONSES[2]);
// Spy on spread method so that we can wait for it to be called.
jest.spyOn(axios, "spread");
});
test("placeholder", async () => {
const { queryAllByTestId } = render(<App />);
await waitFor(() => expect(axios.spread).toHaveBeenCalledTimes(1));
const setups = queryAllByTestId("setup");
const punchlines = queryAllByTestId("punchline");
expect(setups.length).toBe(3);
expect(punchlines.length).toBe(3);
});
});