-
Notifications
You must be signed in to change notification settings - Fork 0
Testing
Documentation for writing and executing tests for this project
In order to start up the test Flask server, we require the setup of these components:
- Flask App object
-
Configobject, responsible for reading environment variables -
RedisClientobject, wrapping theFlaskRedisobject that provides our connection to a Redis instance -
Messagesobject, responsible for handling message strings used within the application -
Boltobject, which is used for communicating with Slack
As the application is dependent on defined environment variables, being able to set them to suit a test case enables a wider scope of testing. To set variables, we use the monkeypatch PyTest Fixture.
import pytest
@pytest.fixture
def custom_fixture(monkeypatch):
monkeypatch.setenv('REDIS_TOKEN', 'REDIS_TOKEN')
monkeypatch.setenv('REDIS_URL', 'redis://REDIS_URL')
monkeypatch.setenv('SLACK_BOT_TOKEN', 'BOT_TOKEN')
monkeypatch.setenv('SLACK_SIGNING_SECRET', 'SLACK_SECRET')
monkeypatch.setenv('SLACK_SCOPES', '["SCOPE_ONE", "SCOPE_2"]')
monkeypatch.setenv('DEBUG', 'False')
monkeypatch.setenv('PORT', '3000')In the example above, we have set the variables referenced by the Config object. If the monkey-patched variables are assigned before the
Config class is imported, then the assigned values will be used when the module does get imported. If not done in the right order, then it
will attempt to find those variables in your host Operating System, and most likely fail to start.
For reference, see v8.0.X Section on 'Monkeypatching environment variables'.
As we will be building the Archbishop blueprint manually, we will be creating the RedisClient that's passed into the construct_blueprint method manually, too.
Since we have access to the client attribute for the RedisClient, we can replace the FlaskRedis/UpstashRedis object with
a FakeRedis object.
from fakeredis import FakeRedis
from flask import Flask
from src.app.util.redis import RedisClient
def some_test_function():
app = Flask(__name__)
# Assumed that Environment Variables are defined already
from src.app.config.config import Config
config = Config()
app.config.from_object(config)
app.app_context().push()
fake_redis = FakeRedis()
redis_client = RedisClient(app, config.REDIS_URL, config.REDIS_TOKEN)
redis_client.client = fake_redis
# Now the get, get_complex, set and set_complex methods will use FakeRedis instead of trying to connect to a real databaseWith this solution, we are able to set the state of the database ahead of the test, then evaluate and make assertions for afterward, while enabling the application to write to the 'database' organically.
Currently, we don't have a working solution for 'sandboxing' the Bolt object that acts as an ambassador between our application and
Slack's API, though there is an idea that's currently being worked on. First, here's what we've discovered about how the Bolt library works:
Internally, the Bolt library uses urllib to send requests to their API at www.slack.com/api/.
This is used inside the WebClient class (client.py), which extends from BaseClient (base_client.py), where most (if not all) calls to the API originate from.
It is for this reason that we are investigating the possibility that we can use patch from unittest.mock
to intercept outbound requests by patching in a mock for the import of urllib in base_client.py.
All going as expected, we would be able to prevent our application from successfully communicating with Slack's API, and
have greater control over how the 'API' would respond, depending on how calls to urllib would be handled. One idea for this is to
replicate a 'Stub' in a similar fashion to how you can use Wiremock in JUnit testing: https://wiremock.org/docs/junit-extensions/
- Note: it takes a while (10+ mins) for the code coverage badge to be updated in the
README.md - Each python directory needs an
__init__.pyto be be treated as a sub-module and be picked up in the code coverage scan