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
This guide is part of the Paystack testing guide.
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