Z is for zip file mocking - Python A to Z
Code in this blog post was written with versions: Python: 3.14, requests-mock: 1.12.1, requests: 2.34.2, pytest: 9.1.1, beautifulsoup4: 4.15.0, lxml: 6.1.2
Python A-Z is a blog series about Python. Each day, I share insights, ideas and examples for different parts of Python development that match with the letter of the day. Blaugust is an annual blogging festival in August where the goal is to write a blog post every day of the month.
Welcome to Z. The last day of our journey through Python from A to Z.
I initially considered writing about zip but I already wrote about zip in Batteries included two years ago and I don’t have anything new to say about it.
A while ago in a project, I had to test a function that downloaded a zip file, read an XML file from it and returned some value. I needed to figure out a way to mock an HTTP GET request to return a zip file.
Setup
In our project, there was a third party library that downloaded a zip file from a hard-coded URL, read a file inside it and returned some data for us. I couldn’t change the function but needed to write a test to keep us safe from changes in that function that would cause hard to catch bugs in our system.
For this example, I’ve created a simplification of that function because what it does is not important for this blog post’s main point.
Here are the required dependencies to run this:
dependencies = [
"beautifulsoup4>=4.15.0",
"lxml>=6.1.2",
"pytest>=9.1.1",
"requests>=2.34.2",
"requests-mock>=1.12.1",
]
If you want to run this thing for yourself, here’s a function that can be used:
# put this in a file: zip_handler.py
import io
import zipfile
from bs4 import BeautifulSoup
import requests
# also needs lxml installed but not imported
# for the xml parser
def handle():
resp = requests.get("https://example.com/example.zip")
with io.BytesIO(resp.content) as f:
zf = zipfile.ZipFile(f)
for filename in zf.namelist():
if filename == "timestamps.xml":
break
out_path = "."
zf.extract(filename, out_path)
with open(filename, "r") as xmlfile:
soup = BeautifulSoup(xmlfile, "xml") # xml parser comes from package lxml
return soup.css.select("timeStamp")[0].text
Mock it
To mock calls to URLs, I used requests-mock that provides a clean interface to deal with this.
First, let’s create our mock data.
Create a file timestamps.xml with the
following
<?xml version="1.0" encoding="iso-8859-1"?>
<timeStamp>10:00:00</timeStamp>
and create a zip file mock.zip with
timestamps.xml inside it
zip mock.zip timestamps.xml
Then create a test file
test_zip_handler.py
from zip_handler import handle
from requests_mock import Mocker
def test_timestamp_read_correctly():
with Mocker() as m:
with open("mock.zip", "br") as zipfile:
m.get("https://example.com/example.zip", body=zipfile)
timestamp = handle()
assert timestamp == "10:00:00"
Here we tell our mocker that we want any call to the specific URL to instead
return the zipfile as the response body. Now, the
handle function will read our data
instead of going to the Interwebs.
Now you can run this test with pytest
uv run pytest # or however you've installed pytest
You should get a green test.
If something above resonated with you, let's start a discussion about it! Email me at juhis@hamatti.org and share your thoughts. This year, I want to have more deeper discussions with people from around the world and I'd love if you'd be part of that.