Comment créer des mocks HTTP en Python

Comment créer des mocks HTTP en Python

Alexandre P. dans Dev - Le 08-02-2024

Lorsque vous codez vos tests unitaires, il arrive que vous ayez besoin que certains appels API ne se déclenchent pas. Surtout si cet API déclenche des appels impactants, suppression, paiement, etc. Cela peut être intéressant de mettre en place un mock. Voici comment procéder...

Pour créer des mocks en Python, je m'appuie sur la librairie responses j'imagine par opposition au paquet requests.

pip install responses
# on n'oublie pas d'installer pytest si ce n'est pas fait
pip install pytest

Nous allons créer une petite lib de test qui fait des appels API, puis mettre en place les mocks pour tester cette lib. Voici l'arborescence:

.
├── libs
│   ├── resource.py
└── tests
    └── test_resource.py

Nous allons créer un contenu très simple pour notre lib resource.py:

# resource.py
import requests

def get_resource(id):
  headers = {
    'Accept': 'application/json'
  }
  response = requests.get('http://localhost:3000/resource/' + id, headers=headers)
  
  if response.status_code != 200:
    raise Exception('Unable to get resource ' + id)
  
  return response.json()


def create_resource(data):
  headers = {
    'Accept': 'application/json',
    'Authorization': 'Bearer blabla' 
  }
  response = requests.post('http://localhost:3000/resource', data=data, headers=headers)

  if response.status_code != 201:
    raise Exception('Unable to get resource ' + id)
  
  return response.json()

Maintenant pour mettre en place nos tests, respectons les conventions de Pytest, nous allons créer un dossier tests et un fichier test_resource.py au sein de ce dossier:

# test_resource.py
import os
import sys
import responses

parent_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
sys.path.append(parent_dir)

from libs.resource import get_resource, create_resource

Maintenant, avant d'aller plus loin, nous allons utiliser un décorateur qui va expliquer à responses que nous souhaitons mettre en place dans cette fonction, l'interception des appels HTTP. Un décorateur c'est une ligne qui se positionne juste avant la déclaration de votre fonction qui viendra altérer le fonctionnement de votre fonction, sans avoir à écrire des lignes entière de code.

@responses.activate
def test_get_resource():

Je vais ensuite déclarer la réponse du mock. C'est à dire, les données que je souhaite manipuler pendant mon test unitaire.

  response_mock = {
    'username': 'Foo',
    'email': 'foo@bar.com',
    'role': 'user'
  }

Puis, on va déclarer dans responses quel appel intercepter afin de donner notre mock en réponse personnalisée en retour:

responses.add(
    responses.get(
      url='http://localhost:3000/resource/3',
      json=response_mock,
      status=200,
      content_type='application/json'
    )
  )

Il ne reste plus qu'à déclencher l'appel et effectuer nos tests:

  resource = get_resource(str(3))
  assert resource['username'] == response_mock['username']

Voici le fonctionnement en gros, et nous appliquons la même logique pour la partie POST. Sans vous faire languir plus longtemps, je vous met directement le fichier entier afin de vous donner une idée du résultat final.

# test_resource.py
import os
import sys
import responses

parent_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
sys.path.append(parent_dir)

from libs.resource import get_resource, create_resource


@responses.activate
def test_get_resource():
  response_mock = {
    'username': 'Foo',
    'email': 'foo@bar.com',
    'role': 'user'
  }

  responses.add(
    responses.get(
      url='http://localhost:3000/resource/3',
      json=response_mock,
      status=200,
      content_type='application/json'
    )
  )

  resource = get_resource(str(3))
  assert resource['username'] == response_mock['username']

@responses.activate
def test_create_resource():
  response_mock = {
    'id': '45',
    'username': 'Blah',
    'email': 'blabla@bar.com',
    'role': 'admin'
  }

  responses.add(
    responses.post(
      url='http://localhost:3000/resource',
      json=response_mock,
      status=201,
      content_type='application/json'
    )
  )

  response = create_resource({
    'username': 'Blah',
    'email': 'blabla@bar.com',
    'role': 'admin'
  })

  assert response['id'] == '45'

Pour lancer les tests, on utilise :

python3 -m pytest -s

C'est tout bon ! J'espère que cet article vous a été utile et qu'il vous aidera à mettre en place vos mocks dans vos projets Python.

#python#tests unitaires#responses#mock#pytest

user picture
Alexandre P.

Développeur passionné depuis plus de 20 ans, j'ai une appétence particulière pour les défis techniques et changer de technologie ne me fait pas froid aux yeux.