Skip to content
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

How to use channel with the test client? #366

Closed
jwg4 opened this issue Nov 22, 2016 · 8 comments
Closed

How to use channel with the test client? #366

jwg4 opened this issue Nov 22, 2016 · 8 comments
Labels

Comments

@jwg4
Copy link
Contributor

jwg4 commented Nov 22, 2016

I am setting up socketio in my Flask factory method like this:

socketio.init_app(app, message_queue=app.config['REDIS_URL'], channel=app.config['SOCKET_CHANNEL'])

The app works fine. I can also subscribe to the socketio messages outside of Flask in a Python script which does

rc=redis.Redis('pgsql-dev-vm')
ps = rc.pubsub() 
ps.subscribe([CHANNEL_NAME]) # here the channel name is hard-coded

(Being able to do this is the reason I switched to using a Redis message queue, which is also the reason I needed to use channels, as staging and several different production instances all use the same Redis server.

However, my tests using the socketio test client, which used to work before I introduced channels to socketio, now fail, because they didn't get any messages.

The setup of the tests looks like this (after some code which sets up the Flask app):

self.client = self.app.test_client()
self.socket_client = socketio.test_client(self.app)

and the test does something like this

self.client.post(url, data=payload, content_type=content_type)
received = self.socket_client.get_received(self.app.config['SOCKET_CHANNEL'])
self.assertEqual(len(received), 1)

The assert fails where once it used to pass.

I don't see anywhere the channel name can be passed into the socket test client, neither when it is created nor when receiving messages, although the namespace can be passed in. Is it not possible to configure this? Or is it something that the test client should handle without passing in the channel name?

@miguelgrinberg
Copy link
Owner

The channel argument to SocketIO() determines the Redis channel name to use. This obviously makes sense only when using Redis, or other message queue. The argument to get_received() is the namespace name, which is completely different.

I think your confusion starts from thinking that Redis is used during tests, which is incorrect. When you are testing your server, there is no real server interactions, all the sending and receiving is mocked. The message_queue argument is ignored, and so is channel, since no message queue is used.

So basically, your tests do not need to change at all with the introduction of a message queue. Nothing will be sent to the message queue anyway. If you need your tests to send stuff to the message queue, then the test client is not the right approach. Instead, you need to use a real server and a real client, and then use a dedicated Redis channel for your tests to take place.

@jwg4
Copy link
Contributor Author

jwg4 commented Nov 24, 2016

Hi Miguel, thanks for your speedy and helpful reply as always.

What you said about the channels makes sense, thanks. It seems that you are right that this doesn't have anything to do with the specific configuration of the channel. However I have tried to narrow down the error I was getting with my tests, and it looks like it is to do with the redis message queue. Changing the line

socketio.init_app(app,
    message_queue=app.config['REDIS_URL'],
    channel=app.config['SOCKET_CHANNEL']
)

back to

socketio.init_app(app)

in my factory method makes the tests pass again.

Could be this to do with how I set up my test app and client? If you're not aware of any possible gotchas, I am happy to try and construct a minimal replication of this problem when I have the chance.

Thanks in advance.

output of pip freeze

alembic==0.6.5
BeautifulSoup==3.2.1
cairocffi==0.6
CairoSVG==1.0.9
cffi==0.8.6
click==6.6
cssselect==0.9.1
factory-boy==2.5.2
fake-factory==0.5.0
Flask==0.11.1
Flask-Autodoc==0.1.2
Flask-Bootstrap==3.2.0.2
flask-ldap-login==0.3.0
Flask-Login==0.2.11
Flask-Migrate==1.2.0
Flask-Moment==0.4.0
Flask-Redis==0.1.0
Flask-Restless==0.14.2
Flask-Script==2.0.5
Flask-SocketIO==2.7.2
Flask-SQLAlchemy==1.0
Flask-Triangle==0.5.4
Flask-WeasyPrint==0.4
Flask-WTF==0.10.2
gevent==1.1.2
gevent-websocket==0.9.5
greenlet==0.4.4
gunicorn==19.1.1
html5lib==0.999
inflect==0.2.4
ipdb==0.8
ipython==2.1.0
itsdangerous==0.24
Jinja2==2.8
jsonschema==2.4.0
lxml==3.4.1
Mako==1.0.0
MarkupSafe==0.23
mimerender==0.5.4
mock==1.0.1
passlib==1.6.5
prmutils-calendar==20150810
psycopg2==2.5.3
pycparser==2.10
pyperclip==1.5.9
Pyphen==0.9.1
python-dateutil==2.4.2
python-engineio==1.0.3
python-ldap==2.4.18
python-mimeparse==0.1.4
python-socketio==1.6.0
pytz==2014.7
redis==2.10.5
requests==2.4.3
selenium==2.51.1
six==1.10.0
sqlacodegen==1.1.4
SQLAlchemy==0.9.7
tinycss==0.3
WeasyPrint==0.23
Werkzeug==0.11.11
WTForms==2.0.1

@jwg4
Copy link
Contributor Author

jwg4 commented Nov 24, 2016

Hi, I have looked into this further and will post a minimal example. However it seems from my understanding of it now that what I want it to do cannot be done.

You see, I am testing functionality whereby I POST to a HTTP endpoint, and check that an appropriate message is sent out over the socket in response. To do the POST request I use the usual Flask test client. To check for the received message I use the Flask-SocketIO test client.

If I understood correctly, when the Flask app is accessed with the Flask test client, it is not really possible to expect the app to emit a message any different than in production. The Flask test client does not know anything about Flask-SocketIO, and so cannot make sure that socket messages are sent over a test interface rather than to the actual Redis MQ. The Flask-SocketIO client can do this, but only in response to other socket messages, since it doesn't support HTTP requests. The Flask-SocketIO knows to listen on the test interface not the real Redis server, but the messages will not get there.

Perhaps it would be possible for the Flask-SocketIO client to somehow inherit from the Flask test client and support HTTP requests, which if they lead to socket messages would go through the test interface? Otherwise it seems that I have to either a) check for messages using a fully-fledged socketio client in my tests, or b) change the factory function so that it does not configure socketio to use Redis when run in a unit test.

Let me know if this reflects how you see the situation.. Thanks in advance.

@jwg4
Copy link
Contributor Author

jwg4 commented Nov 24, 2016

The example is at https://github.com/jwg4/Flask-SocketIO/tree/socket_testing_example

Copying into the thread: (app.py)

from gevent import monkey
monkey.patch_all()

from flask import Flask, request
from flask_socketio import SocketIO

socketio = SocketIO()

def create_app(): 
    app = Flask(__name__)

    # The test succeeds with the following line:
    #socketio.init_app(app)
    # The test fails with the following line:
    socketio.init_app(app, message_queue='redis://pgsql-dev-vm:6379/', channel='foo_test')

    @app.route('/admin', methods = ['GET', 'POST'])
    def admin_page():
        socketio.emit('reboot', None)
        
        return 'Hello'

    return app

if __name__ == '__main__':
    app = create_app()
    socketio.run(app, host='0.0.0.0', port=5021)

(test_app.py)

import unittest

from app import create_app, socketio

class TestSocketApp(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.app = create_app()
    
    def setUp(self):
        self.client = self.app.test_client()
        self.socket_client = socketio.test_client(self.app)

    def test_undelete_event(self):
        payload = '{"command": "comment foo", "auth": "1234", "name": "Jack"}'
        response = self.client.post('/admin', data=payload)
        self.assertEqual(response.status_code, 200)
        received = self.socket_client.get_received()
        self.assertEqual(len(received), 1)

(requirements.txt)

click==6.6
Flask==0.11.1
Flask-SocketIO==2.7.2
gevent==1.1.2
greenlet==0.4.10
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
python-engineio==1.0.3
python-socketio==1.6.0
redis==2.10.5
six==1.10.0
Werkzeug==0.11.11

@miguelgrinberg
Copy link
Owner

miguelgrinberg commented Nov 24, 2016

@jwg4 Your testing configuration should have REDIS_URL set to None. If not, even though the queue is not used, the SocketIO instance created during tests will connect to it. I suspect the failures you are seeing occur because in your test environment, the redis URL that you are passing to SocketIO does not represent a running Redis server.

@jwg4
Copy link
Contributor Author

jwg4 commented Nov 25, 2016

Your first statement is right, and this is how I will try to get the tests to work. Your last statement is not right. I get an exception if the redis URL is not valid. But if it is valid, the test still fails because no message is received.

@miguelgrinberg
Copy link
Owner

But if it is valid, the test still fails because no message is received.

I will check this. Configuring Redis to be used alongside the test client is a combination that does not make sense to use, so it is possible Redis interferes somehow with the mocking that the test client does.

@jwg4
Copy link
Contributor Author

jwg4 commented Nov 25, 2016

Thanks a lot for your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants