How to test your AWS KMS code using Moto and Pytest
Introduction
AWS’ Boto library is used commonly to integrate Python applications with various AWS services. I have generally avoided writing unit-tests for application code that interacts with the boto library because of the complexity involved in mocking and testing these functions.
However, I recently tried out the Moto library which makes it easy to mock AWS services and test code that interacts with AWS.
Some of the benefits of using Moto:
- Testing code that interacts with AWS. Instead of having to test your code in an AWS environment, test AWS interactions locally.
- Easy to learn and get started with.
- Extensive coverage of AWS services.
In this article, we will look at how to add unit tests for AWS KMS using Moto.
Prerequisites
We need to install the following libraries to run the application:
- boto3: AWS SDK for Python
- moto: Mock AWS Services
- Pytest: Test framework for Python
These libraries can be installed via pip:
pip install boto3 moto pytest
Getting Started
We will test the functionality that we added in this AWS KMS tutorial.
To test our KMS code, let’s create a file called test_kms.py
with the following content:
import base64
import boto3
import pytest
from moto import mock_kms
@mock_kms
def test_list_keys():
conn = boto3.client("kms","us-west-2")
key = conn.create_key(Description="my key")
response = conn.list_keys()
assert len(response["Keys"]) == 1
@mock_kms
def test_describe_key():
conn = boto3.client("kms","us-west-2")
key = conn.create_key(Description="my key")
key_id = key["KeyMetadata"]["KeyId"]
key = conn.describe_key(KeyId=key_id)
assert (
key["KeyMetadata"]["Description"] == "my key"
)
@mock_kms
def test_generate_data_key():
conn = boto3.client("kms","us-west-2")
key = conn.create_key(Description="my key")
key_id = key["KeyMetadata"]["KeyId"]
data_key = conn.generate_data_key(
KeyId=key_id, NumberOfBytes=32,
)
# Plaintext must NOT be base64-encoded
with pytest.raises(Exception):
base64.b64decode(data_key["Plaintext"], validate=True)
@mock_kms
def test_decrypt_data_key():
conn = boto3.client("kms","us-west-2")
key = conn.create_key(Description="my key")
key_id = key["KeyMetadata"]["KeyId"]
data_key = conn.generate_data_key(
KeyId=key_id, NumberOfBytes=32,
)
encrypted_data_key, plaintext_data_key = (
data_key["CiphertextBlob"], data_key["Plaintext"]
)
response = conn.decrypt(CiphertextBlob=encrypted_data_key)
assert response["Plaintext"] == plaintext_data_key
Things to note:
test_list_keys
: In this test, we assert that the list of keys returns the key we just created.test_describe_key
: In this test, we assert that they key we created has the expected attributes.test_generate_data_key
: In this test, we check that we can generate a data key using the CMK we created earlier.test_decrypt_data_key
: In this test, we assert that the encrypted data key can be decrypted correctly.
Testing
We can run the unit tests by running the following command:
pytest test_kms.py
=============================================== test session starts ===============================================
platform linux -- Python 3.7.3, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /home/abhishek/Projects/aws-projects/aws-boto-unit-tests
collected 4 items
test_kms.py .... [100%]
================================================ warnings summary =================================================
test_kms.py::test_list_keys
/home/abhishek/.virtualenvs/aws-projects/lib/python3.7/site-packages/boto/plugin.py:40: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
-- Docs: https://docs.pytest.org/en/stable/warnings.html
========================================== 4 passed, 1 warning in 0.51s ===========================================
Conclusion
Moto and Pytest make it pretty easy to unit test AWS-specific application code. It has already helped me become more confident about code that is being deployed to production and makes it possible to test such locally instead of in an AWS environment.