question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[proposal] Chalice Test Client

See original GitHub issue

Test Client Proposal

This proposal outlines improvements to Chalice that enable users to easily test their applications.

Goals

  • Add new Chalice APIs for easily testing a Chalice application.
  • Must support not only REST APIs, but all other Chalice event handlers. Even if all event handlers are not implemented initially, the design of the APIs should be finished.
  • We want the API to feel familiar to python developers. While we will need to adapt to Lambda, we don’t want to stray too far from existing testing APIs that developers use in other frameworks. This API should not have a steep learning curve.

Non Goals

Part of successfully testing an app requires the ability to swap out objects with mock/fake versions. This is out of scope for this proposal, but is addressed in a separate follow up proposal. This proposal focuses on new testing APIs through clients that allow you to invoke your lambda handlers and rest/websocket APIs.

Prior Work

The proposal mostly builds off that work and generalizes it to include testing for all of Chalice’event handlers.

Flask

Docs for testing flask apps: https://flask.palletsprojects.com/en/1.1.x/testing/

Flask lets you test through a context manager, the basic test looks like:


def test_foo():
    with mymodule.app.test_client() as client:
        rv = client.get('/')
        assert b'my test' in rv.data

They also have some documentation on how to use this with pytest. There’s also a more low level context manager that lets you set the context:

import flask

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    assert flask.request.path == '/'
    assert flask.request.args['name'] == 'Peter'

Django

Django also has a test client:

>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
b'<!DOCTYPE html...'

Specification

Most of the frameworks I looked at have a dedicated module for testing:

  • Flask and Sanic includes the test_client directly on the app, along with a corresponding test module.
  • Django has a django.test client.
  • FastAPI has a fastapi.testclient.

Given we’re careful about what goes into the Lambda deployment package (and therefore the app.py file), I think we should have a separate chalice.test module, but I think we shouldn’t add anything on to the app object itself. The Chalice app object should remain as minimal as possible. This means usage will require you to import the test module and instantiate a client by providing your own app object.

Proposed Testing API

There will be a Client class that accepts a chalice.Chalice object as input. It will then have various attributes and methods you can invoke to test your app. There’s a separate events, http, ws, and lambda_ (unfortunate name I know) attributes that allow you to test the different type of event handlers. For lambda_ testing, the main test method is invoke. It accepts the function name (not the fully qualified name though with the app and stage prefixes) as input. Examples:

Testing a pure lambda function:


app = Chalice('myapp')

@app.lambda_function()
def myfunction(event, context):
    return {'event': event}


with chalice.test.Client(app.app) as client:
    result = client.lambda_.invoke('myfunction', {'hello': 'world'})
    assert result.body == {'event': {'hello': 'world'}}

Testing an event handler:


@app.on_sns_message(topic='mytopic')
def handle_sns_message(event):
    return json.loads(event).get('JobId')

with chalice.test.Client(app) as client:
    result = client.lambda_.invoke(
        'handle_sns_message',
        client.events.generate_sns_event(message='{"JobId": "myid"}',
                                         subject='bar'))

    # A future enhancement might allow you to specify emitting an
    # event and Chalice figuring out what handles to invoke.
    client.events.emit_sns_event(
        topic='mytopic', message='foo', subject='bar')

The http client mimics the flask/django/requests API with methods mapping to HTTP methods. They return a response similar to request’s Response class:

Testing a REST API:


@app.route('/')
def index():
    return {'hello': 'world'}

@app.route('/name/{name}')
def hello(name):
    return {'hello': name}


with chalice.test.Client(app) as client:
    response = client.http.get('/')
    assert response.status_code == 200
    assert response.json_body == {'hello': 'world'}

Internally this uses the LocalGateway, so it supports everything that local mode supports and we can ensure we always have feature parity between those local mode and this test client.

The test client also honors your chalice config so you can test env vars and various configuration set for a lambda function. For example, given a .chalice/config.json file of:

{
    "version": "2.0",
    "app_name": "testenv",
    "stages": {
        "prod": {
            "api_gateway_stage": "api",
            "environment_variables": {
                "MY_ENV_VAR": "TOP LEVEL"
            },
            "lambda_functions": {
                "bar": {
                    "environment_variables": {
                        "MY_ENV_VAR": "OVERRIDE"
                    }
                }
            }
        }
    }
}

and an app of:

@app.lambda_function()
def foo(event, context):
    return {'myvalue': os.environ.get('MY_ENV_VAR')}

@app.lambda_function()
def bar(event, context):
    return {'myvalue': os.environ.get('MY_ENV_VAR')}

You’d get these results:

def test_env_vars():
    with Client(app, stage_name='prod') as client:
        assert client.lambda_.invoke('foo', {}).payload == {
            'myvalue': 'TOP LEVEL'
        }
        assert client.lambda_.invoke('bar', {}).payload == {
            'myvalue': 'OVERRIDE'
        }

And lastly, testing a websocket handler involves emulating the connection tracking so that the app.websocket_api.send does what you’d expect.

Testing a websocket handler:



@app.on_ws_message()
def on_message(event):
    app.websocket_api.send(connection_id=event.connection_id,
                           message=event.body)


with chalice.test.Client(app) as client:
    conn1 = client.ws.new_connection(connection_id='12345')
    # connection_id is optional
    conn2 = client.ws.new_connection()

    conn1.send('hello world')
    conn2.send('hello world conn2')
    conn2.send('hello world conn2 again')

    assert conn1.recv() == 'hello world'
    assert conn1.recv() is None
    assert conn2.recv() == 'hello world conn2'
    assert conn2.recv() == 'hello world conn2 again'
    assert conn2.recv() is None

cc @kyleknap @stealthycoin

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:10
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

3reactions
davidolmocommented, Sep 8, 2020

this new testing feature really makes a difference. I love Chalice, and probably it is one of the very few things tying me to AWS, but debugging/performing CI/CD tests in pure lambdas and SQS-triggered lambdas required some nasty workarounds that didn’t cover all use cases. I’ve been trying this in the new version and you just gave me more reasons to love this package. Just wanted to comment to say thanks!

2reactions
ricky-sbcommented, Aug 7, 2020

May I propose the ability to set a custom context on the LocalGateway? This would make it easy to test endpoints which require Cognito. In pytest-chalice, it’s possible like this:

client.custom_context = {'authorizer': {'claims': {'email': 'email@example.com'}}}
response: ResponseHandler = client.get('/')
assert response.status_code == HTTPStatus.OK

There was some discussion on implementing it directly in Chalice here.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Introducing the AWS Chalice test client
This new test client allows you to test REST APIs as well as all the event handlers for AWS Lambda supported by Chalice....
Read more >
Testing — AWS Chalice
test that you can use to test your Chalice applications. This client lets you invoke Lambda function and event handlers directly, as well...
Read more >
Newest 'chalice' Questions - Stack Overflow
I have a small lambda function that I am creating with AWS Chalice in Python. When testing locally all works as intended and...
Read more >
awslabs/chalice - Gitter
... way to test lambda invocation directly. legalizenet. @legalizenet. For local API Gateway/REST route unit testing, im following code as per this ...
Read more >
Getting started with Chalice to create AWS Lambdas in Python
Using Chalice, you can write a Lambda function, test it locally, ... API with the endpoint / which returns a JSON object to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found