Jest with WebSockets ignores messages after the first one? - javascript

I am trying to test a WebSockets server. The onMessage handler gets called for the first time only - any further messages published to the server are not logged. Why is this happening?
`
const WebSocket = require('ws');
const ws = new WebSocket(WEBSOCKETS_ENDPOINT);
describe('When a speed test is triggered', () => {
test('Then AWS should send the result to the client', done => {
ws.on('open', async () => {
ws.send(JSON.stringify({
message: 'start-speed-test-rbsid',
data: RBSID,
platform: "android"
}));
ws.on('message', data => {
data = JSON.parse(data)
// this gets called only once - any further messages published from the server are not logged
console.log(data)
});
})
})
afterAll(() => ws.close())
});
Message data should be logged on every message that was published

I have no problem using you code against echo server.
const WebSocket = require('ws');
const ws = new WebSocket('wss://echo.websocket.org');
describe('When a speed test is triggered', () => {
test('Then AWS should send the result to the client', done => {
ws.on('open', async () => {
ws.send(JSON.stringify({
message: 'start-speed-test-rbsid',
data: 'RBSID',
platform: "android"
}));
ws.on('message', data => {
// this gets called only once - any further messages published from the server are not logged
console.log(data)
});
ws.send(JSON.stringify({
message: 'start-speed-test-rbsid',
data: 'RBSID',
platform: "android"
}));
})
})
afterAll(() => ws.close());
});
Another thing is, if you are writing unit test, and the test involves network IO, it is usually suggested to mock the network component.
const WebSocket = require('ws');
jest.mock('ws', () => {
class MockedWebSocket {}
return MockedWebSocket;
});
console.log(WebSocket); // mocked

Related

Client to retrieve data from backend server database table via a websocket connection

I am using the following server code to retrieve data from a postgres db:
const express = require('express')
const app = express()
const server = require('http').createServer(app);
const pool = require("postgresql");
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server:server });
const getTempData = async () => {
try {
const tempData = await pool.query("select country, temp from my_temp_table");
return JSON.stringify(tempData.rows)
} catch(err) {
console.error(err.messasge);
}
}
wss.on('connection', async (webSocketClient) => {
console.log('A new client Connected!');
const tempDetails = await getTempData();
webSocketClient.send(tempDetails);
webSocketClient.on('message', (message) => {
console.log('received: %s', message);
});
});
server.listen(3000, () => console.log(`Listening on port :3000`))
Now on the client side, I have created the following websocket connection to localhost 3000.
When first rendering the below client code, the data displays where I also get all the console log messages, i.e. ws opened, getting data.... and finally console logging the actual data.
isPaused is also set to false.
The problem I'm facing and unsure what the issue is, is that I expected to see my client page update the country/temp data (no page refresh), when I updated the country/temp values in my_temp_table database table, but it didn't.
The result that I expected was that via the websocket, anytime my table on the server-side updated, the client would update the tempData, via the second useEffect hook below.
I basically would like the client to pull in and display changes from the server via websocket when the data changes in the backend db table.
import React, { useState, useEffect, useRef } from 'react';
export default function Temperature() {
const [isPaused, setPause] = useState(false);
const [tempData, setTempData] = useState([]);
const [name, setName] = useState(null);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket("ws://localhost:3000");
ws.current.onopen = () => {
console.log("ws opened");
}
ws.current.onclose = () => console.log("ws closed");
return () => {
ws.current.close();
};
}, []);
useEffect(() => {
if (!ws.current) return;
ws.current.onmessage = e => {
if (isPaused) return;
console.log("getting temp data....");
const data = JSON.parse(e.data);
setTempData(data)
console.log("data: ",data);
};
}, [isPaused]);
return (
<div>
<button onClick={() => setPause(!isPaused)}>
{isPaused ? "Resume" : "Pause"}
</button>
{ tempData?
tempData.map((data, i) => (
<div>
<span>{data.country}</span>
<span>{data.temp}</span>
</div>
))
: null }
</div>
)
}
The code is executing only once because there are no recurrying calls to the web socket send event. When the web socket is created it gets the data from the database and sends it, and thats it.
You probably want some kind of action that triggers this event multiple times. For example, in your code:
wss.on("connection", async webSocketClient => {
console.log("A new client Connected!");
setInterval(() => {
const timeNow = Date.now();
webSocketClient.send(
JSON.stringify([
{ country: "country-a", temp: timeNow },
{ country: "country-b", temp: timeNow },
])
);
}, 1000);
webSocketClient.on("message", message => {
console.log("received: %s", message);
});
});
I see you are using some package to pool from a PostgreSQL db. Take a look at this other example.
How would your clients know if there is any change in database on server side ?
You can create an event that triggers each time a particular data changes and listen to those event on your client sockets. Like you did with onmessage event in your current code.
You can render the react component based on this event.

How to correctly inititate socket.io connection on the client side with ReactJS?

Hi I'm making a card game and i've been able to connect my client side with my socket.io server but the problem is my client sends a lot of requests when connecting to the server.
I'm using ReactJS on the client side and ExpressJS + socket.io on the server side.
On the client side:
const Room = () => {
const dispatch = useAppDispatch();
const params = useParams() as any;
const user = useAppSelector(selectUser);
const room = useAppSelector(selectRoom);
const [socket] = useState<Socket<DefaultEventsMap, DefaultEventsMap>>(io('http://localhost:3000'));
useEffect(() => {
if (params.id) {
dispatch(getRoom(params.id));
}
return () => { socket.disconnect(); };
}, []);
useEffect(() => {
if (user.name && room.id) {
socket.emit(JOIN_ROOM, {
user: {
name: user.name,
money: user.money,
},
roomId: room.id,
random_seat: room.random_seat,
max_number_of_player: room.max_number_of_player,
});
}
return () => {
socket.emit(LEAVE_ROOM, {
username: user.name,
roomId: room.id,
});
};
}, [user.name, room.id]);
And on the server side:
const onConnection = (socket) => {
roomHandler(io, socket, store);
chatHandler(io, socket);
socket.on('disconnect', function() {
console.log("User has disconnected: " + socket.id)
});
}
When i reload the page i see this log:
User has disconnected: mxh8AqLWSvpB9IqtAAAs
User has disconnected: KefLTWmzHwt4yxi7AAAt
User has disconnected: cNyOJtqX4gLRlkSFAAAv
User has disconnected: hWjpCSx6-fypEcp1AAAu
User has disconnected: D407Grg1YgpLz6V-AAA2
User has disconnected: KN5gWEZSkI4tvqZ2AAA1
User has disconnected: 6QY_pzmugv7hQuZiAAA0
User has disconnected: nunKDWVRiishLsCbAAA3
So when i reload, only one user has disconnected but along with it are multiple socket connections.
I'm initiating the connection by using useState and store the client socket instance as a state which is passed down as props so children component can use it and emits/listens to event.
If i open my network tab in the browser i can also see lots of request made to my server side.
What am i doing wrong here?
Every time this component renders, it will call io('url') and create a new connection. Instead, create the connection in useEffect once and keep a reference.
const socket = useRef();
useEffect(() => {
socket.current = io('url');
return () => {
socker.current.disconnect()
};
}, []);

Cannot read property $socket of undefined - test socket.io

I'm using VueJS and socket.io to generate a message to display to all of the users of my application.
My message is emitted from my action of my store.
It's working well but when I test my code, I have an error: "cannot read property $socket of undefined" (comes from my action).
My main.js:
/*Socket*/
import io from 'socket.io-client'
if(window.location.hostname === 'localhost'){
//Dev
console.log('Dev')
Vue.prototype.$socket = io(`http://localhost:3000`)
}else{
//Prod
console.log('Prod')
Vue.prototype.$socket = io.connect(window.location.hostname)
}
Vue.use(Vuelidate)
Vue.use(BootstrapVue)
Vue.use(BootstrapVueIcons)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
My action of my store:
const addPick = function(context) {
//console.log('actions - ADD PICK');
let request_body = {
game_id : context.state.selectedGame.id,
player_id : context.state.pickedPlayer.id,
user_id : context.state.user.id
}
axios.post(`api/add_picked_player`,request_body).then(response => {
console.log(response.data);
//Building of the message to display to the other players
let object_to_emit = {};
object_to_emit = {
message : context.state.pickedPlayer.name + ' has been selected by '+context.state.user.username,
game_id : context.state.selectedGame.id
}
this._vm.$socket.emit('player_added',object_to_emit);
this.dispatch('getDraftList'); //to update the player available in the draft
this.dispatch('getUserListInGame'); //to update the user information in the game in the UserLineUp component
context.commit('UpdateUserListInGame',context.state.userListInGame);
this.dispatch('getPickedTeamByUser'); //to update the team picked of the user
}).catch(error => console.log(error));
};
My test:
import io from 'socket.io-client'
/*Mocking of Socket io*/
let mockSocket = io('http://localhost:3000');
let message = "message to send";
jest.mock("socket.io-client", () => {
const socket = {
emit: jest.fn(() => {
return new Promise((resolve) => {
resolve(true)
})
})
}
return jest.fn(() => socket);
})
describe('addPick', () => {
test('Success: should add a player and update information (player available, user in the game, team picked by users) in the store', async () => {
const context = {
state: {
selectedGame: {
id:119
},
pickedPlayer: {
id:1
},
user: {
id:1
},
userListInGame: [
{
id:1
},{
id:19
}
]
},
commit: jest.fn()
}
const response = {
data: "the draft is not finished"
};
//const dispatch = jest.fn();
axios.post.mockResolvedValue(response) //OR axios.post.mockImplementationOnce(() => Promise.resolve(response));
await actions.addPick(context)
expect(axios.post).toHaveBeenCalledWith("api/add_picked_player",{
"game_id" : context.state.selectedGame.id,
"player_id" : context.state.pickedPlayer.id,
"user_id":context.state.user.id
});
expect(axios.post).toHaveBeenCalledTimes(2)
//expect(context.commit).toHaveBeenCalledWith("UpdateUserListInGame", context.state.userListInGame)
expect(io).toHaveBeenCalledWith(`http://localhost:3000`)
expect(io).toHaveBeenCalledTimes(1)
await mockSocket.emit("player_added",message)
expect(mockSocket.emit).toHaveBeenCalledTimes(1)
expect(mockSocket.emit).toHaveBeenCalledWith("player_added",message)
//expect(context.commit).toHaveBeenCalledWith("UpdateUserListInGame", context.state.userListInGame)
});
I mocked Axios and my axios request is called successfully.
Same for my emit function of io socket.
The test goes successfully for now but:
I have the error message in my console.log of my action: "cannot read property $socket of undefined"
When I try to test expect(context.commit).toHaveBeenCalledWith("UpdateUserListInGame", context.state.userListInGame), I have an error because of my socket io. This error is the context.commit is not called. (This test is successful without all the message emitted and my socket io part)
What is wrong?
The way I parameter my socket IO in my main.js ?
The way I call my emit() function ? Do I have another possibility of using "this._vm" instead?

How to get websocket url using nestjs and how to test in postman?

I am create mean stack application using nestjs.In nest js i am using websockets.I don't know how to test websockets in postman. Normally i have test route url in postman and get o/p Like: "http://localhost:3000/{routeUrl}" but how to give using sockets i am confused
example :
#WebSocketGateway()
export class MessagesGateway implements OnGatewayDisconnect {
constructor(#InjectModel(Message) private readonly messagesModel:
ModelType<Message>,
#InjectModel(Room) private readonly roomsModel: ModelType<Room>,
#InjectModel(User) private readonly usersModel: ModelType<User>) { // <1> }
async handleDisconnect(client: Socket) { // <2>
const user = await this.usersModel.findOne({clientId: client.id});
if (user) {
client.server.emit('users-changed', {user: user.nickname, event: 'left'});
user.clientId = null;
await this.usersModel.findByIdAndUpdate(user._id, user);
} }
#SubscribeMessage('enter-chat-room') // <3> async
enterChatRoom(client: Socket, data: { nickname: string, roomId: string
}) {
let user = await this.usersModel.findOne({nickname: data.nickname});
if (!user) {
user = await this.usersModel.create({nickname: data.nickname, clientId: client.id});
} else {
user.clientId = client.id;
user = await this.usersModel.findByIdAndUpdate(user._id, user, {new: true});
}
client.join(data.roomId).broadcast.to(data.roomId)
.emit('users-changed', {user: user.nickname, event: 'joined'}); // <3> }
#SubscribeMessage('leave-chat-room') // <4> async
leaveChatRoom(client: Socket, data: { nickname: string, roomId: string
}) {
const user = await this.usersModel.findOne({nickname: data.nickname});
client.broadcast.to(data.roomId).emit('users-changed', {user: user.nickname, event: 'left'}); // <3>
client.leave(data.roomId); }
#SubscribeMessage('add-message') // <5> async addMessage(client:
Socket, message: Message) {
message.owner = await this.usersModel.findOne({clientId: client.id});
message.created = new Date();
message = await this.messagesModel.create(message);
client.server.in(message.room as string).emit('message', message); } }
In your case, when you do not specify the path of the WS, it is listening on the same port as the server listening and path does not matter at all.
WS is a different protocol and you have to make the mental switch to this way of thinking.
You will not be able to test WS server with Postman. To do that you have to create your WS client, however, I strongly encourage you to create E2E tests with Nest to test it with the code, not manually.
Here's my working example:
import * as WebSocket from 'ws'
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [
SocketModule,
],
})
.compile()
app = moduleFixture.createNestApplication()
app.useWebSocketAdapter(new WsAdapter(app))
await app.init()
})
it('should connect successfully', (done) => {
const address = app.getHttpServer().listen().address()
const baseAddress = `http://[${address.address}]:${address.port}`
const socket = new WebSocket(baseAddress)
socket.on('open', () => {
console.log('I am connected! YEAAAP')
done()
})
socket.on('close', (code, reason) => {
done({ code, reason })
})
socket.on ('error', (error) => {
done(error)
})
})
Detail about this answer is discussed here
The second args of cb from onConnection
incomingMessage
this.server.on('connection', (socket:WebSocket, incomingMessage) => {
console.log(incomingMessage)
console.log('connection')
})
incomingMessage.url
'/v2/ta-websocket/22950b69-7928-43b9-8c38-efc5c126208e'
screen shot here

Mocha Async before() hook timing out

Background
I am creating a dummy server using net from Node.js in my Mocha test.
I have a dummy test, and I want to start the server before the test starts, and kill it after:
"use strict";
/*global describe, it, expect, before, after*/
const net = require("net");
describe("dummy server test", () => {
const dummyReader = {
IP: "localhost",
port: 4002,
server: undefined,
socket: undefined
};
before("Starts dummy server", done => {
dummyReader.server = net.createServer(socket => {
dummyReader.socket = socket;
done();
});
dummyReader.server.listen(dummyReader.IP, dummyReader.port);
});
after("Kills dummy server", done => {
dummyReader.server.close();
dummyReader.socket.destroy();
done();
});
it("should pass", () => {
expect(true).to.be.true;
});
});
Problem
The problem is that my async before hook never completes. For a reason I can't understand done is never called, and thus the hook times out.
I tried increasing the time out, believing it could fix the issue, but to no avail.
Question
How can I fix my code?
There are two problems with your code:
You need to flip the host address and port arguments in dummyReader.server.listen(...);. The port comes first, and the host second.
The callback to net.createServer won't be called until something actually connects to the server, but you have nothing connecting to it.
With the following before hook the code will run. I've added code that creates a connection right away for illustration purposes.
before("Starts dummy server", done => {
dummyReader.server = net.createServer(socket => {
dummyReader.socket = socket;
done();
});
dummyReader.server.listen(dummyReader.port,
dummyReader.IP,
undefined,
() => {
// For illustration purposes,
// create a connection as soon
// as the server is listening.
net.connect(
dummyReader.port,
dummyReader.IP);
});
});
Seems to me though that what you should be doing is ending the before hook as soon as the server is listening and then connect to it in your tests. Here's an illustration of how it can be done:
"use strict";
/*global describe, it, expect, before, after*/
const net = require("net");
describe("dummy server test", () => {
const dummyReader = {
IP: "localhost",
port: 6002,
server: undefined,
socket: undefined
};
before("Starts dummy server", done => {
dummyReader.server = net.createServer(socket => {
console.log("got a new socket!");
dummyReader.socket = socket;
});
dummyReader.server.listen(dummyReader.port,
dummyReader.IP,
undefined,
() => {
done();
});
});
after("Kills dummy server", done => {
dummyReader.server.close();
// dummyReader.socket.destroy();
done();
});
let prevSocket;
it("should pass", (done) => {
net.connect(dummyReader.port, dummyReader.IP, () => {
console.log(dummyReader.socket.address());
prevSocket = dummyReader.socket;
done();
});
});
it("should pass 2", (done) => {
net.connect(dummyReader.port, dummyReader.IP, () => {
console.log(dummyReader.socket.address());
console.log("same socket?",
prevSocket === dummyReader.socket);
done();
});
});
});
Each time you connect, a new net.Socket object is created and assigned to dummyReader.socket and so you can access it from inside you test if needed. I've peppered the code with console.log statements to show some key values. When I run it here, I get:
dummy server test
got a new socket!
{ address: '127.0.0.1', family: 'IPv4', port: 6002 }
✓ should pass
got a new socket!
{ address: '127.0.0.1', family: 'IPv4', port: 6002 }
same socket? false
✓ should pass 2
2 passing (71ms)

Categories

Resources