miguelgrinberg / flask-sock

Modern WebSocket support for Flask.
MIT License
274 stars 24 forks source link

test client #23

Open lawndoc opened 2 years ago

lawndoc commented 2 years ago

Does this Flask extension work with Flask's built in test client? Or does that need to be implemented in this project?

lawndoc commented 2 years ago

Based on my (failed) testing, it looks like I'll need a Sock.test_client(app) method for what I'm trying to do. Unless I'm doing something wrong.

If you are busy with other things, I can try to help implement it. If you know of examples in other repos that I can reference that would be a big help.

Edit: maybe something like your Flask-SocketIO test client?

lawndoc commented 2 years ago

I did look into using Flask-SocketIO to get the test client functionality, but my use case requires a long-lived, fully duplexed connection so I can perform a server push when something happens on the backend (not socket event-based).

This seemed like the easier way to go because I'm much more familiar with socket programming than trying to get that to work with socket.io. I also want to keep the particular component I'm referring to as lean as possible because it will have the greatest impact on server performance.

miguelgrinberg commented 2 years ago

There is no support for the test client at this time, sorry.

You can implement your own testing set up, for example by calling your websocket endpoint with a mocked ws object that allows you to carry out your tests.

lawndoc commented 2 years ago

Gonna leave a helpful message here in case anyone is trying to do the same thing as me. It might spare them the time it took me to figure all this out.

You're going to end up needing to bypass the @sock.route() decorator on your function to test it, or you'll get some Flask error about the environment not being valid when you call the function. To call the function directly and bypass the decorator, you just need to import the function you're testing into your test script and call its __wrapped__ attribute.

Since we don't have the decorator creating and passing in a simple_websocket.Server object, you also need to implement your own mock websocket server object that can perform the send and recv calls in your function. Miguel mentioned this in his above comment.

Here's an example flask-sock route and a unit test for it:

routes.py

@sock.route("/api/ws")
def websockRoute(ws):
    while True:
        msg = ws.receive()
        if msg == "foo":
            ws.send("bar")
        try:
            return ws.mockServer
        except AttributeError:
            pass

test_routes.py

from routes import websockRoute

class MockServer:
    def __init__(self):
        self.lastMessage = None
        self.mockServer = True
    def send(self, msg):
        self.lastMessage = msg
    def receive(self):
        return "foo"
    def close(self, *args, **kwargs):
        return

def testWebsockRoute():
    websockRoute.__wrapped__(MockServer())

I found it helpful to keep track of the last message that was sent by the route function for my use case. You'll need to modify the sends and receives to create valid (or intentionally invalid) data for the function you're testing.

I also added the mockServer attribute to our mock ws object so we can stop the while loop during testing, but continue to iterate when actually running the Flask app.

miguelgrinberg commented 2 years ago

Thanks for posting your solution for others to see!

I do not recommend that you access the __wrapped__ element, though. You are using private parts of the Flask and Flask-Sock implementations that can change at any time.

The preferred solution is to call the function directly, as Flask does when it dispatches a request. I think all you need to do to avoid errors is to install a test request context before you call the function.