Moto, Pytest, and AWS Databases: A Quality and Data Engineering Crossroads

The hows and whys of Moto and Pytest with AWS databases

Taylor Wagner
Towards Data Science

--

Photo by Christina @ wocintechchat.com on Unsplash

Overview

I’ve recently worked a lot with Pytest to test AWS services using Boto3. Boto3 is a very powerful AWS SDK for working with AWS services. My research and hands-on experience with Boto3 lead me to discover a complimentary tool called Moto! Moto, in partnership with Boto3, became my go-to for test strategy planning as well as maintaining clean production data in my recent project. In this article, I will share the hows and whys for mock testing AWS databases using Moto (a faker Boto3) with Pytest. I’ll even take you step-by-step through the process of testing an AWS NoSQL database service — Amazon DynamoDB — with Moto and Pytest.

Please note: All images unless otherwise noted are by the author.

Moto

Moto is a Python library that has the ability to mock programmatic usage of AWS services. To lay it out very simply — Boto3 is an object-oriented API for creating, configuring, and managing real AWS services and accounts, and Moto is a mock version of Boto3. While Moto doesn’t include every method Boto3 offers, Moto does provide a good percentage of Boto3 methods in order to run tests locally. When working with Moto, I highly recommend using Boto3 documentation in conjunction with the Moto docs for more detailed explanations of methods.

The advantages of Moto:

  • AWS account not required
  • Avoid the risk of changing actual data or resources in an AWS production account
  • Easily convert Moto mock set ups into Boto3 for real world use-cases
  • Fast running tests — no latency concerns
  • Easy to learn and get started with
  • Free! — will not incur any costs from AWS

A disadvantage of Moto:

  • Doesn’t include all of Boto3 methods — Boto3 offers more extensive coverage of AWS services
Moto Documentation Screenshot: The full list of Boto3 methods that are available with AWS DynamoDB with the methods available for Moto “X” marked
Moto Documentation Screenshot: The full list of Boto3 methods that are available with Amazon DynamoDB with the methods available for Moto “X” marked. For a full view, click here.

The Moto documentation provides insight into which Boto3 methods that are available for use. For example, a little over half of the DynamoDB methods from Boto3 are available to mock out using Moto.

Pytest

Pytest is a Python framework used for writing small and readable tests. This framework uses detailed assertion introspection, making plain assert statements very user-friendly. Pytest has the powerful capability to be scaled to support complex, functional testing of applications and libraries.

Terminal Screenshot 1: Execute pytest command

Tests can be executed with a simple pytest command. The pytest command runs all tests but individual test files can be run with the filepath after pytest, like so: pytest tests/test_dynamodb.py. Running pytest will give you accurate results whereas each test will yield either a green-colored “.” for pass, a red-colored “F” for fail, or a yellow-colored “S” for skip (if applicable). There are two super helpful flags that I’m going to share and highly recommend putting to use when executing tests with Pytest. Please note: The code does not change at all between the times these different commands were run across the screenshots in this section. The only difference is the flags.

Terminal Screenshot 2: Execute pytest command with -s flag

First is the -s (stdout/stderr output) flag. Running pytest -s will display all print statements in the terminal when tests are run. Without the addition of the -s flag, even if there are print statements in the code, no print statements will appear in the terminal. This is very useful for debugging purposes.

Terminal Screenshot 3: Execute pytest command with -v flag

Another useful flag is the -v (verbose) flag. Using pytest -v will provide many more granular details in the terminal when tests are run. This flag will provide the class and method names of each test, an accumulated percentage of each test’s completion in the scheme of all tests to be ran, as well as a green “PASSED”, red “FAILED”, or yellow “SKIPPED” indicator with each individual test. Notice that this flag does not show print statements.

Terminal Screenshot 4: Execute pytest command with combined -sv flags

Pro-tip: It is possible to combine flags when running the test execution command! When working with Pytest, I typically run pytest -sv. The combination of the -s and -v flags together provide the details that I’m looking for with greater readability as well as insight into print statements.

Getting Started

Pytest requires the use of Python3.7+. You can download the latest version of Python here. Then, it will take three libraries to get started. Open your IDE of choice and in a brand new directory, run the following terminal command to download the needed dependencies:

pip install boto3 moto pytest

Next, we set up our “client connection”. The process for setting up a Moto connection is very similar to Boto3. For Boto3, AWS credentials are required. In order to mock this process using Moto, we will just use fake credentials. Feel free to set the values to whatever you like as long as the type is a string.

Please note: In a real use-case, it would be important to keep confidential credential values protected and not hard-code them into an application. Variables can be exported via the command line or saved in an environment file to limit accessibility. Also, Any region can be used for the connection, but “us-east-1” is typically used as that region includes all AWS services and offerings.

Pytest utilizes fixtures in a file named conftest.py to share across modules. In the root of your application, create the file by running this command:

touch conftest.py

and add in the following content to configure the client connection:

import boto3
import os
import pytest

from moto import mock_dynamodb


@pytest.fixture
def aws_credentials():
"""Mocked AWS Credentials for moto."""
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_SECURITY_TOKEN"] = "testing"
os.environ["AWS_SESSION_TOKEN"] = "testing"


@pytest.fixture
def dynamodb_client(aws_credentials):
"""DynamoDB mock client."""
with mock_dynamodb():
conn = boto3.client("dynamodb", region_name="us-east-1")
yield conn

Both the AWS credentials function and the mock DynamoDB client function are implementing the Pytest fixture decorator so that they may be leveraged in other files of the application. The mock DynamoDB client function uses Moto to create a fake client connection to AWS, similarly to how it is done in Boto3. For the purpose of this article, we will try out Moto with DynamoDB as an example AWS service. Look into the Moto docs if you have interest in using Moto for other AWS services.

Testing DynamoDB with Moto

Go ahead and create another file called test_dynamodb.py in the root directory of your project with the following command:

touch test_dynamodb.py

The first step in the test file is to create a DynamoDB table. Since many methods in Moto require a table already created, we will create a very basic test table that will be leverage when using various methods to avoid the need of creating a table in every single test case. There are a few strategies for creating this test table, but I’m going to show you how to create the table by implementing a context manager. The great news is that context managers come from Python’s native library, requiring no additional package downloads. The test table can be created like this:

After creating the test table in a single function with a context manager, we can go ahead and test that this test table was indeed created. The table name of the test table will be a required parameter for many Moto DynamoDB methods, so I’ll create this as a variable for the entire test class to avoid repetition and for better readability. This step isn’t necessary, but it helps save time and lowers the chance of syntax errors from having to continuously re-write value of the table name when calling different methods.

In the same test_dynamodb.py file, below the the context manager, create a test class so that all related tests (tests for the “my-test-table”) can be grouped together.

As you can see above, the dynamodb_client argument is passed through the test method as an argument. This comes from the conftest.py file, where we already established the mock connection to Amazon DynamoDB. Line 4 displays the optional declaration of the table name variable. Then on line 9, we call the context manager into the test_create_table method in order to access the “my-test-table”.

From there, we can write a test to validate that the table was successfully created by calling the .describe_table() and .list_tables() methods from Moto and asserting that the table name exists in the response output. To run the test simply execute the following command (you may include additional flags if you so wish):

pytest

This command should yield the result of a passing test:

Execute pytest command for testing the creation of the table, 1 passing test
Terminal Screenshot 5: Execute pytest command for testing the creation of the table

Further DynamoDB Testing with Moto

Your test file is now ready for any additional DynamoDB test methods that you want to explore using Moto, such as adding an item to the “my-test-table”:

The test you see above can be placed inside the TestDynamoDB class directly under the test_create_table method. The test_put_item method also utilizes the context manager to use the same test table that was originally created at the top of the file. At the start of each method and with the call of the context manager within each test suite, the “my-test-table” is in it’s original status upon first creation. Please note: state does not persist between test methods.

I have created a GitHub repository that encompasses the sample tests included in the article with the addition of much more! My example code expands on the testing in the article to include the following DynamoDB tests:

  • Creating a table
  • Putting an item into the table
  • Deleting an item from the table
  • Deleting a table
  • Adding table tags
  • Removing table tags

This repository also includes some testing for AWS RDS. In my example code, I organized test files into its own directory and grouped tests for consistencies in testing purposes. I cherry-picked different methods for each service to showcase the range of what Moto offers. There are many more methods that Moto has available that are not included in my example code. Be sure to check Moto’s documentation!

Final Thoughts

Plugging in Pytest fixtures and Python context managers, Pytest’s ease of use and read-to-scale framework coupled with Moto is a great combination for validating the usage of AWS’s database services. While the Moto/Pytest cocktail may not be a sufficient drink of choice for testing real AWS services and accounts, it’s a sufficient option for practicing and gaining a better understanding of how you might test your real AWS account without the threat of security breaches, tampering important data, or running up an expensive, unnecessary bill. Just keep in mind that Moto has a limited range of methods as compared to Boto3. It has been my experience that working with Moto’s mocked responses will provide you the understanding needed to utilize Boto3 for real AWS use-cases. It is my hope that my article and example code can help you with your AWS database needs.

--

--