Bonaventure OgetoBy Bonaventure Ogeto|

Mocking the Paystack API in Pytest

In Python, mock Paystack API calls using @patch("requests.post") or @patch("requests.get") decorators in pytest. Return a Mock object with a .json() method that returns your fake Paystack response. For webhook testing, use hashlib and hmac to generate a valid signature in your test, then POST to your webhook view.

Mocking Paystack API Calls with unittest.mock

# tests/test_payment.py
import pytest
from unittest.mock import patch, MagicMock


@patch('myapp.payments.requests.post')
def test_initialize_transaction_returns_auth_url(mock_post):
    mock_response = MagicMock()
    mock_response.json.return_value = {
        'status': True,
        'data': {
            'authorization_url': 'https://checkout.paystack.com/test',
            'reference': 'ref_test_001',
        }
    }
    mock_post.return_value = mock_response

    from myapp.payments import initialize_transaction
    result = initialize_transaction(email='user@test.com', amount=100000)

    assert result['data']['authorization_url'] == 'https://checkout.paystack.com/test'
    mock_post.assert_called_once()
    call_kwargs = mock_post.call_args[1]
    assert 'Bearer' in call_kwargs['headers']['Authorization']


@patch('myapp.payments.requests.get')
def test_verify_transaction_success(mock_get):
    mock_response = MagicMock()
    mock_response.json.return_value = {
        'status': True,
        'data': {
            'status': 'success',
            'amount': 100000,
            'reference': 'ref_test_001',
        }
    }
    mock_get.return_value = mock_response

    from myapp.payments import verify_transaction
    result = verify_transaction('ref_test_001')

    assert result['data']['status'] == 'success'

Testing Webhook Handlers in Django

# tests/test_webhook.py
import hashlib
import hmac
import json
import pytest
from django.test import TestCase, Client


class PaystackWebhookTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.secret = 'sk_test_fake_secret'

    def make_signature(self, body: str) -> str:
        return hmac.new(
            self.secret.encode('utf-8'),
            body.encode('utf-8'),
            hashlib.sha512
        ).hexdigest()

    def test_valid_charge_success_webhook(self):
        payload = {
            'event': 'charge.success',
            'data': {
                'reference': 'ref_001',
                'amount': 100000,
                'status': 'success',
                'metadata': {'order_id': 1},
            }
        }
        body = json.dumps(payload)
        sig = self.make_signature(body)

        response = self.client.post(
            '/webhook/paystack/',
            data=body,
            content_type='application/json',
            HTTP_X_PAYSTACK_SIGNATURE=sig,
        )
        self.assertEqual(response.status_code, 200)

    def test_invalid_signature_rejected(self):
        payload = {'event': 'charge.success', 'data': {}}
        response = self.client.post(
            '/webhook/paystack/',
            data=json.dumps(payload),
            content_type='application/json',
            HTTP_X_PAYSTACK_SIGNATURE='invalid_sig',
        )
        self.assertEqual(response.status_code, 401)

Learn More

Key Takeaways

  • Use @patch("requests.post") to intercept outbound calls to the Paystack API in Python tests.
  • The mock must have a .json() method — use MagicMock and set return_value.json.return_value to your fake response dict.
  • Test webhook signature validation in Python: hmac.new(secret.encode(), body.encode(), hashlib.sha512).hexdigest().
  • Test both success and failure cases — mock different return values in separate test functions.
  • In Django tests, use self.client.post() to send webhook requests to your view; in Flask tests, use app.test_client().post().
  • Use pytest fixtures to set environment variables (monkeypatch.setenv) for PAYSTACK_SECRET_KEY without affecting other tests.

Frequently Asked Questions

Should I use responses library or unittest.mock for Paystack API mocking in Python?
Both work. unittest.mock.patch is built-in and sufficient for most cases. The responses library (pip install responses) is cleaner if you have many tests mocking different URLs — it matches mock responses to specific URL patterns. For Paystack specifically, @patch("requests.post") is usually enough.
How do I test async Paystack calls with aiohttp in pytest?
Use pytest-asyncio and aioresponses (pip install aioresponses). In your test: from aioresponses import aioresponses. Then: with aioresponses() as m: m.post("https://api.paystack.co/transaction/initialize", payload={...}). This intercepts aiohttp calls without real network requests.
How do I inject the test secret key without hardcoding it?
Use pytest monkeypatch in a fixture: @pytest.fixture(autouse=True) def set_paystack_secret(monkeypatch): monkeypatch.setenv("PAYSTACK_SECRET_KEY", "sk_test_fake"). This sets the environment variable for the duration of the test without persisting it.
What is the correct Python HMAC signature for Paystack webhooks?
import hashlib, hmac. Then: sig = hmac.new(secret.encode("utf-8"), body.encode("utf-8"), hashlib.sha512).hexdigest(). The body must be the exact raw bytes received — do not parse and re-serialize the JSON, as this may change spacing and invalidate the signature.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon