I'm trying to test a click event originated from an icon (<i>) in a Stencil component. The thing is I am able to debug the test and I see that the test triggers the correct method on the component, but Jest is not able to detect\register the event and the toHaveBeenCalled() method fails.
input.spec.jsx
it("dispatches icon click event", async () => {
const page = await newSpecPage({
components: [Input], //just the component name
template: () => (<input-fields value="some input value"></input-fields>),
});
const eventSpy = jest.fn();
page.win.addEventListener('input-field-search-event', eventSpy);
await page.root.shadowRoot.querySelector('.icon-container').click();
await page.waitForChanges();
expect(eventSpy).toHaveBeenCalled();
});
input.tsx
private dispatchSearchEvent(e){
//i can stop ay this point with a break point and the data passed by the test is correct
this.tipInputEnterEvent.emit({type: "input-field-search-event", event: e, data: this.value});
}
the error
● Input tests › dispatches icon click event
expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1
Received number of calls: 0
334 | await page.root.shadowRoot.querySelector('.icon-container').click();
335 | await page.waitForChanges();
> 336 | expect(eventSpy).toHaveBeenCalled();
| ^
337 | });
338 |
339 | });
By default the name of custom events is the property name, in your case tipInputEnterEvent. So the listener should instead be:
page.win.addEventListener('tipInputEnterEvent', eventSpy);
You can also change the event name if you want:
#Event({ eventName: 'input-field-search-event' }) tipInputEnterEvent: EventEmitter;
Related
I have a controller like this
async create(#Body() dto: myDTO,#Req() request, #I18n() i18n?: I18nContext): Promise<MyEntity>
{
return this.Myservice.create(dto, request, i18n);
}
and service like this:
async create(dto: CreateApplicationDTO, request?, i18n?): Promise<Application> {
const MyEntity= this.repository.create(dto);
request.message = i18n.t('common.added_successful');
return this.repository.save(MyEntity);
}
and service.spec file this :
it('check number of created applications', async () => {
await service.create(myDto);
expect(await service.findAll()).toHaveLength(1);
});
When I start test show me this error
TypeError: Cannot set properties of undefined (setting 'message')
41 | return this.repository.save(newApplication);
42 | } catch (e) {
> 43 | return request.message = e.message;
| ^
44 | }
45 |
46 | }
I know I should send request and i18n to service but I don't know how mock them and send them to service in my spec file
You've set request to be optional but set a property on it regardless if it is populated or not. You need to make sure that request has a value before you try to assign to it. For simplicity sake, because you don't give it a type, it could just be {} and then {}.message = will be valid.
As for i18n in your create method you use the object method t so you need the mock to have a t method. Something like { t: jest.fn(() => 'some value') } should be enough to get the test going with a mock.
Having stronger typings will help you though, even if it means it is harder to make mocks. You could use a tool like #golevelup/ts-jest can be used to create full mocks off of types alone
I'm trying to verify a text message on a dialog box using cypress and cucumber. The test cases are working perfectly fine when it's within "it function". Here is is the sample code :
it ('Verify if the Login is successful', function()
{
cy.visit('loginTest.html')
cy.get('#username').type('shahin')
cy.get('#password').type('tala')
cy.contains('Login').click()
cy.on('window:alert', (str) => {
expect(str).to.equal(`Login Successfully`)
})
})
However when i add the BDD keywords it looks like the function doesn't get evaluated at all. It works for When but not the Then scenario. I think it needs to be handled in Js in a different way. I have uploaded the cypress log as well. Below is the code :
When('I click on the login button', () => {
cy.contains('Login').click()
})
Then('Successful POP up message should be displayed', () => {
cy.on('window:alert', (str) => {
expect(str).to.equal(`Login Successfully`)
})
Cypress Log
The first thing is cy.on('window:alert'... is a passive event listener, it does not do anything until an event is emitted by the app.
This means you need to set it before the event is triggered (e.g Login click),
When('I click on the login button', () => {
cy.on('window:alert', ...something here...) // set up the event listener
cy.contains('Login').click() // action that triggers the event
})
If you do your expect() within the callback of the event listener it mucks up your BDD flow (Then() is redundant).
Use a stub to catch the event, and assert the stub properties inside Then().
let stub // declare outside so it's visible in both When and Then
When('I click on the login button', () => {
stub = cy.stub() // set stub here (must be inside a test)
cy.on('window:alert', stub) // capture call
cy.contains('Login').click()
})
Then('message is displayed', () => {
expect(stub).to.have.been.calledWith('Login Successful')
})
Why does it() work?
Essentially, with it() all the code is within one block vs two blocks for When() Then().
The async commands are queued for later exectution, but the synchronous cy.on() is executed immediately - even though it's the last line it gets executed first.
it('...', () => {
// Queued and executed (slightly) later
cy.visit('loginTest.html')
cy.get('#username').type('shahin')
cy.get('#password').type('tala')
cy.contains('Login').click()
// executed immediately (so actually first line to run)
cy.on('window:alert', (str) => {
expect(str).to.equal(`Login Successfully`)
})
})
The When() and Then() blocks are executed in sequence, so you don't get the same pattern as with it().
I am mocking navigator functions for simple clipboard functionality. Here is the relevant code:
// FUNCTION
/**
* Adds a click event to the button which will save a string to the navigator clipboard. Checks for
* clipboard permissions before copying.
*/
function loader(): void {
async function copyUrl(): Promise<void> {
const permission = await navigator.permissions.query({ name: "clipboard-write" });
if (permission.state == "granted" || permission.state == "prompt" ) {
await navigator.clipboard.writeText("the url");
} else {
console.error('Permission not supported');
}
}
const button = document.querySelector('button') as HTMLElement;
button.addEventListener('click', async () => {
await copyUrl();
});
}
// TEST
it('works', () => {
// mock navigator functions
Object.assign(navigator, {
permissions: {
query: jest.fn(async () => ({ state: "granted" }))
},
clipboard: {
writeText: jest.fn(async () => {})
}
});
// initialize DOM
document.body.innerHTML = '<button></button>';
loader(); // adds the event listener
// click the button!
const button = document.querySelector('button') as HTMLElement;
button.click();
expect(navigator.permissions.query).toHaveBeenCalledTimes(1);
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
});
The test fails on expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url') with:
Expected: "the url" Number of calls: 0
Defeats the purpose of permissions, yes, but for the sake of debugging:
Try adding a clipboard call before permissions call like so?
// FUNCTION
// ...
async function copyUrl(): Promise<void> {
// add this
await navigator.clipboard.writeText('the url');
// keep the rest still
const permission = await navigator.permissions.query({ name: "clipboard-write" });
// ...
}
This fails on the first assertion now, expect(navigator.permissions.query).toHaveBeenCalledTimes(1) with
Expected number of calls: 1 Received number of calls: 0
With the addition above, I also changed the assertions to be:
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('the url');
expect(navigator.clipboard.writeText).toHaveBeenCalledTimes(2);
expect(navigator.permissions.query).toHaveBeenCalledTimes(1);
... which failed on the second assertion because it expected 2 calls but only received 1.
I have been testing in a VSCode devcontainer and tried out the extension firsttris.vscode-jest-runner to debug the test. With breakpoints in the loader function, I'm able to see that every single line executes perfectly with my mockup but still fails at the end of debug.
I even changed the mock navigator.permissions.query function to return { state: 'denied' } instead. Both running and debugging, it did not satisfy the permission check and gave an error to the console as expected but the test still failed at expect(navigator.permissions.query).toHaveBeenCalledTimes(1) (with the added writeText call before it).
It seems to me that after the first call of a mock function, the others just don't work.
Am I missing something? Send help pls lol
EDITS
Using jest.spyOn as in this answer has the same issues.
Using an async test with an expect.assertions(n) assertion still produces the exact same issue.
I want to verify/assert the results of spied function. I'm using nestjs framework with jasmine. I create a jasmine spy on a method i want to "spy" on, that is, eavesdrop args and response/exception. However, I can't access return value of spied method.
Let's say I have an emitter and listener and I want to assert that my listener throws an exception when a DB operation fails.
Listener:
onModuleInit() {
this.emitter.on('documentDeleted', d => this.onDocumentDeleted(d));
}
#CatchAndLogAnyException()
private async onDocumentDeleted(dto: DocumentDeletedEventDTO) {
this.logger.log(`Deleting document with id '${dto.id}'...`);
const result = await this.ResearchHearingTestModel.deleteOne({ _id: dto.id });
if (!result.ok) {
throw new DataAccessException(
`Deleting document with id '${dto.id}' failed. Model.deleteOne(id) result: ${result}`,
);
}
if (result.n < 1) {
throw new DocumentNotFoundException(`Deleting document with id '${dto.id}' failed.`);
}
this.logger.log(`Deleted document with id '${dto.id}.`);
}
Test:
const mockId = 123;
const spyDelete = spyOn(model, 'deleteOne').and.returnValue({ ok: 1, n: 0 });
const spyOnDeleted = spyOn(listener, 'onDocumentDeleted');
spyOnDeleted.and.callThrough();
await emitter.emit('documentDeleted', new DocumentDeletedEventDTO(mockId));
expect(spyOnDeleted).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledTimes(1);
expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
expect(spyOnDeleted).toThrow(DocumentNotFoundException);
So when debugging, I can see spyOnDeleted["[[Scopes]]"][0].spy.calls.mostRecent["[[Scopes]]"][0].calls[0].returnValue is a promise i'm probably looking for, but I can't access it or verify on it.
And when I run the test, this is the output:
expect(received).toThrow(expected)
Expected name: "DocumentNotFoundException"
Received function did not throw
95 | expect(spyDelete).toHaveBeenCalledTimes(1);
96 | expect(spyDelete).toHaveBeenCalledWith(expect.objectContaining({ _id: mockId }));
> 97 | expect(spyOnDeleted).toThrow(DocumentNotFoundException);
| ^
98 | });
99 | });
100 | });
I've seen CallThrough injected spy and several other questions that are similar, but I'm still hoping it's possible to spy on callThrough methods and eavesdrop on in/out of it. Any suggestions?
toThrow cannot be used on spies. You can use spies to mock behavior or use the actual behavior with callThrough and then make sure the method was called with specific parameters. But a spy will not have information about the result it produced (value or error) so you cannot set expectations on it.
If you want to test the behavior of onDocumentDeleted you have to either test it indirectly by observing the effects of the method. In your case (with #CatchAndLogAnyException), it seems to write to the log!? So you can spy on the log and expect it to be called with the error message. Or alternatively, you test the method directly by making it public.
I'm new to jest so I'm not sure if its my fault. So here is my test case
it('should throw error if wrong email or phone number is provided', async () => {
await expect(userService.verifyCredentials('invalidemail#invaliddomain.com', 'sayantan94'))
.rejects
.toEqual(new UnauthorizedException('Invalid email or phone number'));
})
But this fails even though the same exception is thrown. Here is the output
FAIL src/modules/user/user.service.spec.ts (5.156s)
● UserService › verifyCredentials › should throw error if wrong email or phone number is provided
expect(received).toStrictEqual(expected)
Expected value to equal:
[Error: [object Object]]
Received:
[Error: [object Object]]
Difference:
Compared values have no visual difference.
88 | await expect(userService.verifyCredentials('invalidemail#invaliddomain.com', 'sayantan94'))
89 | .rejects
> 90 | .toEqual(new UnauthorizedException('Invalid email or phone number'));
| ^
91 | })
92 | })
93 |
How do I check this?
Instead of toEqual try toThrow
it('should throw error if wrong email or phone number is provided', async () => {
await expect(userService.verifyCredentials('invalidemail#invaliddomain.com', 'sayantan94'))
.rejects
.toThrow('Invalid email or phone number');
});
Ref: https://jestjs.io/docs/en/expect.html#rejects