Testing if an event has been triggered in NestJS - node.js

In the project we are building, there is a standard sequence in the workflow.
Controller receives a request
Then fires an event through #nestjs/event-emitter
Event listener listens to it and executes some code depending on the event
How can we properly test if an event has been triggered but without actually executing the code inside?
We are using Jest for testing.
Mocking with Jest doesn't seem to work, because we want to test the actual trigger event and not the result.
Any suggestions?
"node": "16.14.2"
"#nestjs/event-emitter": "^1.1.0",
"jest": "^27.5.1",

Following the suggestion from #ovidijus-parsiunas we managed to spy on the "OnEvent" method successfully.
it.only("start selling", async () => {
const user = await userSeeder.createOne();
const spy = jest
.spyOn(StartSellingListener.prototype, 'startSelling')
.mockImplementation(() => null);
expect(eventEmitter.hasListeners(EventsEnum.USER_START_SELLING)).toBe(true);
const startSelling = userService.startSelling(user);
expect(startSelling).toBeTruthy();
expect(spy).toBeCalledWith(user);
spy.mockRestore();
});

You will have to spy on the handler method that listens to the event or alternatively spy on the encapsulated methods that get called within it. Once your spies are set up you should then be able to trigger the event emitter code from your test and validate if the spies have been triggered.

Related

How to correctly remove event listeners registered on chrome.runtime.onMessage in devtools page?

I created a custom devtool panel in my chrome extension. Within that devtool page I registered some event listeners on chrome.runtime.onMessage in useEffect and I attempt to remove those event listeners during unmount like this:
useEffect(() => {
const handler = (request) => {
// do something when receive message
}
chrome.runtime.onMessage.addListener(handler);
return () => {
// not be called when I close devtool panels
chrome.runtime.onMessage.removeListener(handler);
}
}, []);
However I found that when I close the devtools, the unmount function can not be executed so that I think those event listeners are not removed correctly. I wrote some testing code to verify if those event callbacks are still able to be invoked after devtools being closed. It turns out that those callbacks are not executed. I guess the reason would be that those callback functions are destroyed when I close the devtools along with other resources. Now I am not sure about whether those "ineffective" event callbacks are still in the callback list of chrome.runtime.onMessage. If they are, would it be a trouble? What is the correct way to cleanup those listeners?
Thanks in advance!

E2E Testing of event handlers in NestJS

How can you test that the proper event handler was called after specific event was emitted by an endpoint? (E2E integration test)
It looks like jest does not call EventEmitter2 handlers.
Thx.

Firestore onUpdate function not triggering

The problem
I'm using Firebase cloud functions with the emulator, and for some reason, the onUpdate trigger is not triggering, yet the onCreate function does trigger.
All code is TypeScript, and it's transpiled to JS to work on cloud functions.
// functions/src/music.ts
// this function runs
export const onMusicCreated = functions.firestore
.document('music/{musicId}')
.onCreate(async (snapshot) => {
console.log('on create is running')
})
// this function doesn't run
export const onMusicUpdated = functions.firestore
.document('music/{musicId}')
.onUpdate(async (change) => {
console.log('on update is running')
})
Both functions are async because in the final code,
On the front-end, when I run the add function on the front-end, the onCreate function fires.
const { id } = await firebase.firestore().collection('music').add({ title: 'hello world' })
The console log runs as expected and the emulator outputs this into the console:
i functions: Beginning execution of "onMusicCreated"
i functions: Finished "onMusicCreated" in ~1s
Yet when I update that same document, the onUpdate function doesn't run.
// "id" is the same id as above
await firebase.firestore().doc(`music/${id}`).update({ title: 'new title' })
Nothing happens. I can confirm that the document is actually updated when I look in the firestore emulator. Am I missing something obvious? The front-end code is simplified as compared to my actual use, but the functions code isn't simplified at all. And I can confirm that the firestore document is created and updated as I'd expect.
No error messages in the console or the logs.
Debug steps
I've checked to make sure the functions code was correctly transpiled to JS. I bothed looked at the code output, as well as updated the onCreate code multiple times to ensure that function updated
I hollowed out all my code inside the functions (as shown above), so I can confirm that the function itself isn't running
The onUpdate function technically accepts two parameters. Same results with both parameters.
I have not tried the functions in production, only with the emulator.
Related posts
Why doesn't firestore onWrite trigger get invoked on firebase cloud functions emulator?
I'm not using any forbidden characters in the document selector, or at least I'm not getting that error message.
Firebase Cloud Functions for Firestore are not triggering
Using functions 3.11.0, and the functions are async, hence they should implicitly return Promise. Results are the same when explicetely returning a value (e.g., return 0)
https://firebase.google.com/docs/functions/firestore-events#trigger_a_function_when_a_document_is_updated
That's the official docs. As far as I can tell, I'm doing what the docs say. I could just be missing something blindingly obvious, though.
Other details
macOS Big Sur 11.1 (20C69)
Firebase CLI 9.1.0
The emulator should be up-to-date
Any ideas? Thanks!
Turns out I just forgot to import onMusicUpdated in the index.ts functions file. Hence the emulator never knew it existed!
Changed
// functions/src/index.ts
export { onMusicCreated } from './music'
to
// functions/src/index.ts
export { onMusicCreated, onMusicUpdated } from './music'

Test using mongodb-memory-server is failing/timing out

I’m setting up mongodb-memory-server in my backend for test purposes and am experiencing some issues when running tests that I need to debug. My issue is that when I run my test (which will create a mongodb doc somewhere in the service being tested), the test times out.
As I understand it, this is because when the test is executed and a new mongo doc is trying to be created during the test, I console log mongoose.connection.readyState and it says it’s 0, meaning that mongoose is disconnected. This is strange to me because I added console logs to my connectMongoose() function (pictured below) and it says that mongoose is connected.
So my main question is why does it say mongoose is connected at the end of connectMongoose(), but it says it’s disconnected during the execution of the unit test/service function? How can I ensure that MongoDB-memory-server is fully connected prior to test execution?
Below is a screenshot showing how I am doing the mongoose test connection:
Below this is a screenshot of exactly where and how mongodb-memory-server is being used:
Here is a screenshot of my jest.config.js:
And finally the actual test file which has the failing test (what I’m asking about):
beforeAll(connectMongoose)
beforeEach(clearDatabase)
afterAll(disconnectMongoose)
Your three functions here are async functions, but you don't await them - is it possible that the connect Mongoose returns whilst the promise is still awaiting, and the other code continues despite the async function not having completed yet?
Perhaps this would better serve your purpose?
beforeAll(() => {
await connectMongoose
})
Before :
beforeAll(connectMongoose)
beforeEach(clearDatabase)
afterAll(disconnectMongoose)
After:
beforeAll(async() => {await connectMongoose})
beforeEach(async() => {await clearDatabase})
afterAll(async () => { await disconnectMongoose})
The reason is you should wait until the mongoose connection is done completely and remove
set timeout in connectMongoose function not needed there.If you want to use jest timeout you can use it in the beforeEach function.

Async function in NodeJS EventEmitter on AWS Lambda

I have an AWS Lambda application built upon an external library that contains an EventEmitter. On a certain event, I need to make a HTTP request. So I was using this code (simplified):
myEmitter.on("myEvent", async() => {
setup();
await doRequest();
finishingWork();
});
What I understand that happens is this:
My handler is called, but as soon as the doRequest function is called, a Promise is returned and the EventEmitter continues with the next handlers. When all that is done, the work of the handler can continue (finishingWork).
This works locally, because my NodeJS process keeps running and any remaining events on the eventloop are handled. The strange thing is that this doesn't seem to work on AWS Lambda. Even if context.callbackWaitsForEmptyEventLoop is set to true.
In my logging I can see my handler enters the doRequest function, but nothing after I call the library to make the HTTP call (request-promise which uses request). And the code doesn't continue when I make another request (which I would expect if callbackWaitsForEmptyEventLoop is set to false, which it isn't).
Has anyone experienced something similar and know how to perform an ansynchronous HTTP request in the handler of a NodeJS event emitter, on AWS Lambda?
I have similar issue as well, my event emitter logs all events normally until running into async function. It works fine in ECS but not in Lambda, as event emitter runs synchronously but Lambda will exit once the response is returned.
At last, I used await-event-emitter to solve the problem.
await emitter.emit('onUpdate', ...);
If you know how to solve this, feel free to add another answer. But for now, the "solution" for us was to put the eventhandler code elsewhere in our codebase. This way, it is executed asynchronously.
We were able to do that because there is only one place where the event is emitted, but the eventhandler way would have been a cleaner solution. Unfortunately, it doesn't seem like it's possible.

Resources