Overview
This tutorial shows how to add pytest fixtures that run tests against MongoDB and use
transactions to roll back changes. It also shows how to package a Python library by using
the hatchling library and publish the package to PyPI.
Tip
Coding with Mark
This tutorial is based on the second episode of the "Coding with Mark" livestream. You can watch the recording on the MongoDB YouTube channel.
Fixtures
Fixtures in pytest are functions that create resources for use in unit tests. Typically,
fixtures are defined in a file named conftest.py in your project directory. You can use
a yield statement to return a value to the test function.
Each fixture has a scope, which controls how often the fixture runs.
The following fixture, sample_fixture, uses the default scope, function. This means
that pytest runs the fixture before each test function that uses it. The fixture uses a
yield statement to return the string "Hello, World" to the test function.
def sample_fixture(): # Code before the test runs yield "Hello, World" # Code after the test runs
Prerequisites
Before you begin the tutorial, complete the following steps:
Follow the steps in the Get Started guide to install PyMongo and its dependencies. Be sure to copy your connection string for use in a later step.
Run the following command to install
pytestin your environment:pip install -U pytest Find the MongoDB connection string that you copied in a previous step. Run the following command to store the connection string in the
MDB_URIenvironment variable:export MDB_URI="<your connection string>" Install the
buildandtwinepackages to package and publish your library. Thebuildpackage installs build dependencies and packages your project, and thetwinepackage securely uploads your package to PyPI.python -m pip install --upgrade build twine
Create and Test pytest Fixtures
This section of the tutorial includes the following tasks:
Adding
pytestfixtures for aMongoClientobject and a rollback sessionWriting tests that use these fixtures
Add a MongoClient fixture
The following code sample shows a fixture named mongodb. This
fixture uses the connection string in the MDB_URI environment variable to create
a MongoClient object, then ensures that it can connect to the specified MongoDB
deployment. The fixture has a session scope, which means that pytest runs the
fixture at the start of the test session and reuses it for all tests.
This fixture uses the mongodb fixture by including it as a function parameter.
This instructs pytest to pass mongodb as an argument to the fixture.
Add the following code to a file named conftest.py:
import pytest import pymongo import os def mongodb(): client = pymongo.MongoClient(os.environ["MDB_URI"]) assert client.admin.command("ping")["ok"] != 0.0 yield client client.close()
To test the fixture, add the following code to a file named test_mongodb_fixture.py:
def test_mongodb_fixture(mongodb): """This test passes if `MDB_URI` is set to a valid connection string.""" assert mongodb.admin.command("ping")["ok"] > 0
Run the test by executing the following command in your terminal:
pytest test_mongodb_fixture.py
If the test is successful, it prints a message similar to the following:
============================= test session starts ============================== collected 1 item test_mongodb_fixture.py . [100%] ============================== 1 passed in 0.XXs ===============================
Add a rollback session fixture
If your tests modify data in MongoDB, but you don't want those changes to persist, you can use a transaction to roll back the changes after each test. To do so, perform the following steps in your fixture:
Create a session
Start a transaction
Yield the session to the test
Abort the transaction after the test runs
The fixture in the following code example implements this pattern:
def rollback_session(mongodb): session = mongodb.start_session() session.start_transaction() try: yield session finally: session.abort_transaction()
The preceding fixture uses the default function scope, so abort_transaction
runs after each test function.
To test the fixture, add the following code to a file named test_update_mongodb.py:
def test_update_mongodb(mongodb, rollback_session): my_collection = mongodb["sample_db"]["sample_collection"] my_collection.insert_one( { "_id": "bad_document", "description": ( "If this still exists, then transactions are not working." ), }, session=rollback_session, ) assert ( my_collection.find_one( {"_id": "bad_document"}, session=rollback_session ) is not None )
Note
session Argument
Pass the session argument to all queries. This ensures that they are
part of the transaction.
Run the test by executing the following command in your terminal:
pytest test_update_mongodb.py
If the test is successful, it prints a message similar to the following:
============================= test session starts ============================== collected 1 item test_update_mongodb.py . [100%] ============================== 1 passed in 0.XXs ===============================
Package a Python Library
In this section, you can learn how to package a sample Python library by performing the following actions:
Describe the package
Build the package
Publish the package to PyPI
Describe the package
Describe your project in a file named pyproject.toml. The following file
describes a package that uses the hatchling build backend:
[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "sample_application" version = "0.0.1" authors = [{ name = "<your name>", email = "<your email address>" }] description = "An example application to demonstrate packaging a Python project." requires-python = ">=3.10" dependencies = ["pymongo>=4.15.5"] classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Development Status :: 1 - Planning", "Intended Audience :: Developers", "Topic :: Database", ] [tool.hatch.build.targets.wheel] packages = ["sample_application"] [project.urls] Homepage = "https://github.com/mongodb"
Tip
More pyproject Settings
For more information about writing a pyproject.toml file, see
Writing your pyproject.toml.
Build the package
To build a distribution from your package, run the following command in your project directory:
python3 -m build
The preceding step creates a wheel and gzipped tarball for your package. If the build operation is successful, it prints output similar to the following:
* Creating isolated environment: venv+pip... * Installing packages in isolated environment: - hatchling * Getting build dependencies for sdist... * Building sdist... * Building wheel from sdist * Creating isolated environment: venv+pip... * Installing packages in isolated environment: - hatchling * Getting build dependencies for wheel... * Building wheel... Successfully built sample_application-0.0.1.tar.gz and sample_application-0.0.1-py3-none-any.whl
Publish to PyPI
After building, you can upload your wheel and gzipped tarball by using twine.
To upload these files, run the following command in your project directory:
python3 -m twine upload dist/*
A successful upload produces output similar to the following messages:
* Uploading distributions to https://upload.pypi.org/legacy/ * Enter your username: * Enter your password: * Uploading sample_application-0.0.1-py3-none-any.whl * Uploading sample_application-0.0.1.tar.gz * View at: https://pypi.org/project/sample_application/0.0.1/
More Information
For more information about the Python packages and code used in this tutorial, see the following resources: