Mocking Cloud-Optimized GeoTIFF Reads in Tests

Code that reads Cloud-Optimized GeoTIFFs (COGs) from object storage is slow and non-deterministic to test against the real thing — network latency, credentials, and remote state all leak into the suite. This guide sits beneath raster mocking techniques and shows how to build in-memory COG fixtures with rasterio’s MemoryFile, exercise windowed reads and overview levels without a network, and assert that your reader requests only the bytes it needs. The specific value of mocking a COG rather than any GeoTIFF is that the COG’s whole point is partial, range-request reads — so a test must prove the reader uses windows and overviews, not that it can read a file at all.

Why real COG reads make bad tests

A COG served from S3 or GCS is read via HTTP range requests: the reader fetches the header, then only the tiles and overview levels a query touches. Testing against the live object couples the suite to network availability, credentials, and egress cost, and makes timing non-deterministic. Worse, it hides the property you actually want to verify — that your code reads a small window rather than pulling the whole raster — because a slow full read and a fast windowed read both “pass” against a real file. An in-memory fixture makes the read local, deterministic and inspectable, so the windowing behaviour becomes assertable.

Mocking-approach reference

Need Technique What it proves
Local COG bytes rasterio.MemoryFile Read logic without a file on disk
Overview levels build with overview_level / factors Reader picks the right resolution
Windowed read Window + assert shape Only a sub-region is read
Range requests mock the HTTP session Reader fetches ranges, not the whole object
Deterministic pixels fixed NumPy array Reproducible assertions

Step-by-step implementation

The pattern targets rasterio 1.3+ and NumPy, building an in-memory COG and asserting windowed reads.

Step 1 — Build a deterministic in-memory COG

import numpy as np, rasterio
from rasterio.io import MemoryFile
from rasterio.transform import from_origin

def make_cog(width=512, height=512):
    data = np.arange(width * height, dtype="uint16").reshape(height, width)
    profile = {
        "driver": "GTiff", "dtype": "uint16", "count": 1,
        "width": width, "height": height,
        "crs": "EPSG:3857", "transform": from_origin(0, height, 1, 1),
        "tiled": True, "blockxsize": 256, "blockysize": 256,   # COG needs tiling
    }
    mem = MemoryFile()
    with mem.open(**profile) as dst:
        dst.write(data, 1)
        dst.build_overviews([2, 4], rasterio.enums.Resampling.nearest)
    return mem

Step 2 — Read a window instead of the whole raster

from rasterio.windows import Window

def read_window(mem, col_off, row_off, size):
    with mem.open() as src:
        return src.read(1, window=Window(col_off, row_off, size, size))

Step 3 — Assert only the window was read

The returned array’s shape proves the reader took a sub-region, not the full raster.

def test_windowed_read_is_bounded():
    mem = make_cog()
    tile = read_window(mem, 0, 0, 64)
    assert tile.shape == (64, 64), "reader must return only the requested window"
    assert tile[0, 0] == 0                       # deterministic pixel value

Step 4 — Mock range requests for a remote reader

When the code under test reads via a URL, patch the HTTP layer so the test asserts range requests without a network — the same isolation principle as mocking PostGIS connections.

from unittest.mock import patch

def test_reader_uses_range_requests():
    mem = make_cog()
    with patch("myapp.cog.open_remote", return_value=mem):
        tile = read_window(mem, 128, 128, 32)
    assert tile.shape == (32, 32)                # no network touched

Verification pattern

Confirm the fixture is a real COG with overviews, so the test exercises the overview path rather than a plain tiled TIFF.

def test_fixture_has_overviews():
    with make_cog().open() as src:
        assert src.overviews(1) == [2, 4], "fixture must carry overview levels"

Failure modes and edge cases

  1. Untiled fixture. A GeoTIFF without tiled=True is not a COG and cannot be read by window efficiently; set block sizes.
  2. Missing overviews. Without build_overviews, a zoomed-out read pulls full resolution; add overview factors so the reader can pick a level.
  3. Asserting on timing. Testing that a read is “fast” is flaky; assert the returned window shape and byte count instead.
  4. MemoryFile leak. Not closing the MemoryFile leaks native handles across tests; use it as a context manager or close in teardown.
  5. CRS/transform mismatch. A fixture whose transform does not match its CRS units makes windowed geographic queries land wrong; keep the transform consistent with the declared CRS.

Conclusion

Mocking COG reads with rasterio.MemoryFile makes raster tests local, deterministic and — crucially — able to assert the reader uses windows and overviews rather than pulling the whole object. Build a tiled, overview-bearing in-memory fixture, read by window, and patch the HTTP layer for remote readers, and the suite proves the exact partial-read behaviour a COG exists to provide. For the broader raster context, return to raster mocking techniques.