Jasmine Spy called incorrectly - testing

I'm running into an interesting problem. I have set up a jasmine spy on an event listener that I attach and detach during the lifecycle of the component (this is in lit element by the way). On the connected callback I attach it like this:
getPositionEvent = this.getPosition.bind(this);
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this.getPositionEvent, true);
}
I later detach it like this:
disconnectedCallback() {
window.removeEventListener('resize', this.getPositionEvent, true);
}
I can see during testing of the code that the attachment works here:
let getPositionEventSpy: jasmine.Spy;
beforeAll(() => {
(code setting up component)
getPositionEventSpy = spyOn(component, 'getPositionEvent');
}
beforeEach(async () => {
component.connectedCallback();
await component.updateComplete;
getPositionEventSpy.calls.reset();
})
it('should include an event listener for "resize"', async () => {
window.dispatchEvent(new Event('resize'));
await component.updateComplete;
expect(getPositionEventSpy.calls.count()).toEqual(1);
})
The problem comes when I try to test that the event listener is detached. I put this test in a separate describe block where I initiate a the disconnectedCallback function which should remove the event listener and then test that the spy has not been called when I dispatch the event:
describe('disconnection', () => {
beforeAll(() => {
component.disconnectedCallback();
getPositionEventSpy.calls.reset();
})
it('should remove the "resize" event listener', async () => {
expect(getPositionEventSpy.calls.count()).toEqual(0);
window.dispatchEvent(new Event('resize'));
await component.updateComplete;
expect(getPositionEventSpy).not.toHaveBeenCalled();
})
})
In this case the test fails meaning the getPositionEventSpy has been called. In trying to understand what's happening I added a console.log("getting position") statement in the getPosition() function. When I run the test for removing the event listener the console log statement doesn't get run, so I believe that the removal of the eventlistener is actually successful. So why does the spy count increase? Does anybody know?

try spy.resetHistory()
beforeAll(() => {
component.disconnectedCallback();
getPositionEventSpy.resetHistory();
})

Related

Cypress spy on multiple calls of the same method

I'm trying to check that a method was not called again after a certain action.
My test:
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(1); // a basic check after mounted hook
});
});
});
My component:
async mounted(): Promise<void> {
await this.foo.bar();
}
async getSearchResults(): Promise<void> {
if (this.searchQuery.length < 3) {
return;
}
await this.foo.bar();
}
The problem is that bar was already called on mount, and it could have been called multiple times before, if query length was valid. I was thinking about saving bar's callCount to a variable and checking it after call, but that looks ugly. Kinda stuck here, any ideas are welcome.
It's not an issue. The call count is started at the point you set up the spy, not when the component is mounted.
Try this:
const foo = {
bar: () => console.log('bar called')
}
it('starts with a clean callcount', () => {
foo.bar() // make a call
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
Even if you have some callcount from another test, you can always reset it before the current test:
it('allows reset of spy callCount', () => {
cy.spy(foo, 'bar').as('bar'); // callCount === 0 on setup
foo.bar() // make a call, count is now 1
cy.get('#bar').invoke('resetHistory') // remove prior calls
cy.get('#bar')
.its('callCount')
.should('eq', 0) // passes
});
I believe you can get the initial call count, and then wrap your test in that.
it('if query is less than 3 symbols, api call is not made', () => {
cy.spy(foo, 'bar').as('bar');
cy.get('#bar').its('callCount').then((initRes) => {
cy.get('input').type('12').then(() => {
cy.get('#bar').its('callCount').then(res => {
expect(res).to.eq(initRes); // a basic check after mounted hook
});
});
});
});
You would probably want to do a test that this would fail, to make sure that Cypress is getting '#bar' again.

Vitest LitElement events

I have a storybook project where i am using Vite and LitElement components.
To test the components i thought i would use the Vitest library.
But i can't really test my components, it is like if the components aren't initialized / mounted / working (but they work fine in the stories., so i think the problem is with the testing).
I have a breadcrumb component, which dispatches a custom event on the connectedCallback function. On my story i can listen to this event, so i know it is being dispatched.
But i can seem to test it.
What i have:
on the breadcrumb component, inside the connectedCallback function
this.dispatchEvent(new Event('abc-breadcrumb-connected'));
on my breadcrumb.test.ts file:
import type { IWindow } from 'happy-dom';
import { expect, describe, it, beforeEach, vi } from 'vitest';
import '../abc-breadcrumb';
import { AbcBreadcrumb } from "../abc-breadcrumb";
declare global {
interface Window extends IWindow {}
}
describe('Abc breadcrumb', async () => {
it('Dispatches connected event', async () => {
const mockConnectedCallback = vi.fn(() => true);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
mockConnectedCallback()
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await new Promise(resolve => setTimeout(resolve, 0));
expect(mockConnectedCallback).toHaveBeenCalled();
})
});
0n my vite.config.ts i have:
export default defineConfig({
test: {
globals: true,
environment: 'happy-dom',
},
...
})
the error i get:
AssertionError: expected "spy" to be called at least once
I have no idea why it isn't working an would be really happy to get some help.
Thanks!
At first look, using await new Promise(resolve => setTimeout(resolve, 0)); seems like the kind of thing I was unsuccessfully relying on myself to ensure enough ticks had passed for various operations to complete. This often worked in my local browser but failed in CI or resulted in flaky tests.
Why not set up the promise such that the event listener calls resolve() or possibly even mockConnectedCallback(). Then you can be certain the event isn't firing as opposed to only not having been fired when setTimeout resolves.
const mockConnectedCallback = vi.fn(() => true);
let connectedResolve;
const connectedPromise = new Promise(r => connectedResolve = r);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
connectedResolve();
mockConnectedCallback();
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await connectedPromise;
expect(mockConnectedCallback).toHaveBeenCalled();
It makes the expect() a little redundant given that it won't be reached until the awaited promise resolves, but I think awaiting an explicit resolution makes things easier to reason about and doesn't hard-code various assumptions about microtask queues and things into the test code, which more often than not have come back to bite me later.

Unit test jest enzyme throws error on Formik 'resetForm'

I am trying to run unit test (enzyme) throws error on Formik 'resetForm'.
TypeError: Cannot read property 'resetForm' of undefined
FormikForm.js
_handleSubmitPress = (values, { resetForm }) => {
const { onSubmit } = this.props;
if (onSubmit) {
onSubmit({ ...values, resetForm });
}
};
UnitTest.js:
it('Should fire formik form submit', () => {
const UpdateButtonPressMock = jest.fn();
const component = Component({
onSubmit: UpdateButtonPressMock,
});
expect(component.find(Formik)).toHaveLength(1);
component.find(Formik)
.first()
.simulate('Submit');
expect(UpdateButtonPressMock).toHaveBeenCalled();
});
I couldn't find any solution for this error.
Could someone help me on the above? I would really appreciate any help.
According to official docs for simulate, the function signature accepts an optional mock event.
The code you are testing uses properties that are not included in the default SyntheticEvent object that ReactWrapper passes to your event handler by default, for instance event.resetForm.
One way to do this is by triggering Formik's onSubmit directly like so:
// UnitTest.js
.simulate("submit", { resetForm: whateverYourMockResetFormValueShouldBe })
component.find(Formik)
.first()
.prop('onSubmit')(valuesMock, { resetForm: UpdateButtonPressMock });
expect(UpdateButtonPressMock).toHaveBeenCalled();
I haven't tested this, but you should be able to pass the event along with simulate as well.
// UnitTest.js
component.find(Formik)
.first()
.simulate("submit", { resetForm: UpdateButtonPressMock })
expect(UpdateButtonPressMock).toHaveBeenCalled();

How can I `await` closing of window in Spectron?

I'm working on Electron application that handles some logic in modal windows.
Those windows are waiting for async actions to resolve and then self-close. Now I am struggling to test this behaviour using Spectron and Jest: seems that there are no methods to catch window's closing and then proceed to another tests.
Currently my code is
it('doing its job', async () => {
// awaits and expects that aren't related
await app.client.click('button[data-role="close"]');
await new Promise(r => setTimeout(r, 1000));
expect(await client.getWindowCount()).toBe(1);
});
It works but I find it extremely anti-pattern. I wonder if there are any methods to do something like
it('doing its job', async () => {
// awaits and expects that aren't related
await app.client.click('button[data-role="close"]');
await app.client.waitUntilWindowCloses(windowIndex);
expect(await client.getWindowCount()).toBe(1);
});
Any help is appreciated.
await app.client.waitUntil(async () => (await app.client.getWindowCount()) === 1);

Detox: "Cannot find UI element." when trying to scroll FlatList

This is the code for the tests:
//navigates to the new screen:
it("should show myFlatListScreen after tap", async () => {
await element(by.id("navigationButton")).tap();
await waitFor(element(by.id("myFlatListScreen"))).toBeVisible();
});
//Passes without issue:
it("FlatList should be visible", async () => {
await waitFor(element(by.id("myFlatList"))).toBeVisible();
});
//Fails with: "Cannot find UI element." error
it("FlatList should scroll", async () => {
await element(by.id('myFlatList')).scroll(100, 'down');
});
How is it that the element can pass the toBeVisible() test and then not exist for scrolling?
EDIT: I figured it out. there is some code before these that looks like this:
beforeEach(async () => {
await device.reloadReactNative();
});
The app is reloading from the start each time which is why that element is no longer available. It looks like I have to write all my tests so they run start to finish for each.
There is some code before these that looks like this:
beforeEach(async () => {
await device.reloadReactNative();
});
The app is reloading from the start each time which is why that element is no longer available. It looks like I have to write all my tests so they run start to finish for each.