Create a Mock GitHub Release API in Python for Testing gh CLI

Build an in-memory GitHub Releases API mock that mimics create_release and list_releases for unit testing gh CLI stubs without network calls.

Easy Python 3.9+ Aug 9, 2026 Git + Python 16 views 0 copies

Python code

47 lines
Python 3.9+
import json
from unittest.mock import patch, Mock

class GitHubReleaseAPI:
    """Mock GitHub Releases API for testing gh CLI stub behavior."""
    
    def __init__(self):
        self.releases = {}
        self.counter = 1
    
    def create_release(self, repo, tag, name=None, notes=None):
        release_id = self.counter
        self.counter += 1
        release = {
            "id": release_id,
            "tag_name": tag,
            "name": name or tag,
            "body": notes or "",
            "draft": False,
            "prerelease": False,
            "html_url": f"https://github.com/{repo}/releases/tag/{tag}"
        }
        self.releases[release_id] = release
        return release
    
    def list_releases(self, repo):
        return [r for r in self.releases.values() if repo in r["html_url"]]


def main():
    api = GitHubReleaseAPI()
    
    # Create test releases
    rel1 = api.create_release("octocat/hello-world", "v1.0.0", 
                              notes="First stable release")
    rel2 = api.create_release("octocat/hello-world", "v1.1.0",
                              notes="Bug fixes and improvements")
    
    print("Created releases:")
    print(json.dumps([rel1, rel2], indent=2))
    
    print("\nList of releases:")
    print(json.dumps(api.list_releases("octocat/hello-world"), indent=2))


if __name__ == "__main__":
    main()

Output

stdout
Created releases:
[
  {
    "id": 1,
    "tag_name": "v1.0.0",
    "name": "v1.0.0",
    "body": "First stable release",
    "draft": false,
    "prerelease": false,
    "html_url": "https://github.com/octocat/hello-world/releases/tag/v1.0.0"
  },
  {
    "id": 2,
    "tag_name": "v1.1.0",
    "name": "v1.1.0",
    "body": "Bug fixes and improvements",
    "draft": false,
    "prerelease": false,
    "html_url": "https://github.com/octocat/hello-world/releases/tag/v1.1.0"
  }
]

List of releases:
[
  {
    "id": 1,
    "tag_name": "v1.0.0",
    "name": "v1.0.0",
    "body": "First stable release",
    "draft": false,
    "prerelease": false,
    "html_url": "https://github.com/octocat/hello-world/releases/tag/v1.0.0"
  },
  {
    "id": 2,
    "tag_name": "v1.1.0",
    "name": "v1.1.0",
    "body": "Bug fixes and improvements",
    "draft": false,
    "prerelease": false,
    "html_url": "https://github.com/octocat/hello-world/releases/tag/v1.1.0"
  }
]

How it works

The GitHubReleaseAPI class simulates the GitHub REST API's releases endpoints with an in-memory dictionary keyed by release ID. create_release auto-increments an internal counter to assign unique IDs and builds a response dict matching GitHub's JSON schema. list_releases filters stored releases by checking whether the repo name appears in the generated html_url, mimicking GitHub's organization of releases per repository. Since everything lives in memory, tests run fast and deterministically without network calls or rate limits. The json.dumps calls in main format output identically to real API responses, so your gh CLI stub can consume the mock exactly like production data.

Common mistakes

  • Forgetting to increment the counter, so every release gets the same ID and overwrites previous entries.
  • Filtering on `repo` directly instead of checking `html_url`, which breaks if repo strings vary in casing or slashes.
  • Not resetting the mock between tests, causing state leakage and cross-test contamination when the class is reused.
  • Returning `None` instead of an empty list from `list_releases` when no releases match the repo.

Variations

  1. Subclass `unittest.mock.Mock` and configure side effects to simulate network failures or rate-limit errors.
  2. Use a file-backed store or SQLite instead of a dict to persist releases across test runs.

Real-world use cases

  • Unit-testing a gh CLI wrapper that creates release notes without hitting the real GitHub API in CI.
  • Simulating release workflows in integration tests where multiple services consume the same mock API response shape.
  • Developing and validating changelog generation scripts before deploying them to a production GitHub Actions pipeline.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Git + Python

Related tutorials and quizzes for this topic.