-
-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fastify.inject like testing for WS? #35
Comments
That would be a fantastic feature! Would you like to send a PR? The way I would build this is to use https://github.com/mafintosh/duplexify to create a couple of "inverted" duplex streams from a couple of |
Hey @mcollina, indeed it would be a useful feature. So far I haven't had a look in the source code of Fastify, so I decided to have a look in both Fastify and Duplexify, to evaluate a potential contribution via a PR. At first glance this seems too overwhelming, considering other tasks on my plate. Maybe if you help me out providing some hints where to start looking in the source code options for checking |
Essentially you'll need to add a decorator that calls https://github.com/fastify/fastify-websocket/blob/master/index.js#L63-L67 with the right arguments. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
There's also the |
That'd work! Would you like to send a PR? |
Ideally, I'd be very happy with something like const ws = fastify.injectWebSocket('/my/ws/path')
ws.send('hello world') // just a normal WS instance without having to touch the original plugin options at all. I don't think this would be doable with the |
I have not designed this :(. It probably requires some analysis and experimentation. |
I looked into this a bit but stopped once it started feeling like stacking hacks on top of hacks. I got to the point where it kind of manages to setup a WS connection, but sending data doesn't quite work yet. Maybe this will be a useful starting point for something else fastify.decorate('injectWebSocket', opts => {
const client = new WebSocket(null)
const server2client = new PassThrough()
const client2server = new PassThrough()
const serverStream = new class extends duplexify {
constructor() {
super(server2client, client2server)
}
setTimeout() {}
setNoDelay() {}
}
const clientStream = new class extends duplexify {
constructor() {
super(client2server, server2client)
}
setTimeout() {}
setNoDelay() {}
}
let serverClient
wss.handleUpgrade({
method: 'GET',
headers: {
connection: 'upgrade',
upgrade: 'websocket',
'sec-websocket-version': 13,
'sec-websocket-key': randomBytes(16).toString('base64')
},
}, serverStream, Buffer.from([]), (ws, req) => { serverClient = ws })
let resolve
const promise = new Promise(_resolve => resolve = _resolve)
function recvData(d) {
if (d.toString().includes('101 Switching')) {
clientStream.removeListener('data', recvData)
client.setSocket(clientStream, Buffer.from([]))
client._isServer = false // yikes
resolve(client)
}
}
clientStream.on('data', recvData)
// TODO how to get the route handler
testesttest(WebSocket.createWebSocketStream(serverStream))
return promise
}) |
Implemented message handling it in my project so I will share results. I hope it will be helpful for implementation: Test in jestimport WebSocket from 'ws'
import fastifyWebsocket, { SocketStream } from '@fastify/websocket'
import fastify from 'fastify'
import { Reactivity } from '../src/services/Reactivity'
describe('websocket', () => {
it('basic', async () => {
let expectedMessage = ''
const app = fastify()
app.register(fastifyWebsocket)
const rawContext: { connection: SocketStream | null } = {
connection: null,
}
const [ctx, establishConnection] = Reactivity.useFirstChangeDetector(rawContext)
app.get('/ws', { websocket: true }, (connection) => {
connection.socket.on('message', async (message) => {
expectedMessage = message.toString()
})
ctx.connection = connection
})
await app.ready()
const address = await app.listen({ port: 0, host: '0.0.0.0' })
new WebSocket(`${address.replace('http', 'ws')}/ws`)
await establishConnection;
ctx.connection?.socket.emit('message', 'xd')
await app.close()
expect(expectedMessage).toEqual('xd')
})
}) and import { EventEmitter } from 'node:events'
export class Reactivity {
static useFirstChangeDetector<T extends object>(target: T): [T, Promise<void>] {
const e = new EventEmitter()
const handler: ProxyHandler<T> = {
set(obj, prop, value) {
e.emit('change', prop, value)
return Reflect.set(obj, prop, value)
},
}
const proxy = new Proxy(target, handler)
return [
proxy,
new Promise((resolve) => {
e.on('change', () => {
resolve(undefined)
})
}),
]
}
} with his own test import dayjs from 'dayjs'
import { sleep } from './helpers'
import { Reactivity } from '../src/services/Reactivity'
describe('reactivity', () => {
it('i am able to react on change in object instantly instead of using setTimeout', async () => {
const target = {
value: 'Why do programmers prefer dark mode?',
}
const [proxy, promise] = Reactivity.useFirstChangeDetector(target)
let t2: dayjs.Dayjs = dayjs()
promise.then(() => {
t2 = dayjs()
})
const t1 = dayjs()
await sleep(1)
proxy.value = 'Because light attracts bugs.'
await sleep(1)
const t3 = dayjs()
expect(t1.valueOf()).toBeLessThan(t2.valueOf())
expect(t2.valueOf()).toBeLessThan(t3.valueOf())
expect(proxy.value).toEqual('Because light attracts bugs.')
})
}) with sleep function as export const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms)) What I mostyly dislike in my code
I can't do it without reservation of port. If you can please share it with me. Without these lines Btw this proof of concept shows that we are able to test messages to websocket. |
Looked a bit into it trying to come up with a solution but hit a wall trying to have fastify-websocket working without reserving a port or starting the server. Also tried replacing the inner server with a mocked one provided externally through options but it isn't enough to solve this issue since fastify ws routes aren't really attached until the server is running. |
I think what this feature should do is to emit an Line 53 in f7da62d
If the API match up, every should line up correctly. |
i tried emitting the |
Hey guys,
for faking HTTP requests, there is ab option to inject fake HTTP requests via light-my-request. Looking in the tests of this repo, I can see that Fastify really needs to listen on some port in order to create a WS client connetion.
Is there a way to Inject a WS connection without actually making Fastify server listen on a port?
I'm asking because right now we many HTTP tests using Ava testing framework, that are in many separate files (due to concurrent testing). So far for HTTP testing we were not forcing the Fastify server to listen on any port. Now we are at the point where we want to start cover the WS scenarios with such tests and it seems that we are going to need to actually make Fastify server listen on certain port.
The issue we are seeing is that each test file will need to have a separate port, because they run concurrently. I know it's doable like that, but I'm just currious if it's possible to fake WS connections as well as WS data being sent?
Thanks in advance.
/GJ
The text was updated successfully, but these errors were encountered: