Flask CRUD JSON API - testing your API

Flask API Unit Testing

Testing


- Case
# given# when# thenassert true

Kacper Walczak · 01-05-2024

Learn how to unit test your APIs.

Introduction

This is the third part of the series about Flask JSON API.

You don't need to have any previous code. But if you want to catch up, you can follow the first/second article or clone the repository from GitHub (opens in a new tab).

This article is a part of the Flask JSON API series.

Unit testing

Unit testing is a type of testing that tests individual units of code. In this case, we will unit test our endpoints.

Endpoints make calls to a services, grabs/persists data, and returns it.

We should test if the endpoint internal service calls are correct and if the response is as expected.

What to test?

Let's look at the following endpoint and get_data(data_id, user) method.

class Service:
    def __init__(self, repository, notifier):
        self.repository = repository
        self.notifier = notifier
 
    def get_data(self, data_id, user):
        data = self.repository.get(data_id)
        self.notifier.notify(data, { "fetch_by": user })
        return {"list": data}
 
def endpoint(request, data_id):
    repo = Repository()
    notifier = Notifier()
    service = Service(repo, notifier)
    data = service.get_data(data_id, request.user)
    return data.get("list", [])

Testing Service

To test service we should test if:

  • service.get_data is called and returns proper data for a given set of arguments
  • notifier is called with proper arguments, but we can omit it due to the fact that this will be tested in the integration test like: test_get_gata_and_notify and we want to unit test whole flow not side effects

Make another test for the notifier if you want to test it separately with e.g. mocks.

Testing get_data

To test get_data we need testing repository and notifier.

class Service:
    def get_data(self, data_id, user):
        data = self.repository.get(data_id)
        self.notifier.notify(data, { "fetch_by": user })
        return {"list": data}

Once we get here we can understand that we have to:

  • make an abstraction for repository and notifier services.
Repository and Notifier ABC

These are abstract classes that we can use to define the interface for the repository and notifier.

They allow us to easily swap the implementation of the repository and notifier. We can use the actual repository and notifier in the production code and the testing repository and notifier in the testing code.

It is way better than using mocks.

We will be able to REUSE the testing repository and notifier in other tests.

from abc import ABC, abstractmethod
from typing import List, Any
 
class RepositoryABC(ABC):
    @abstractmethod
    def get(self, data_id) -> List[Any]:
        pass
 
class NotifierABC(ABC):
    @abstractmethod
    def notify(self, data, meta):
        pass
Testing Repository and Notifier

With this approach we can easily stub the repository and notifier services with an actual testing the system.

class TestRepository(RepositoryABC):
    data = {
        "1": [1]
    }
 
    def get(self, data_id):
        return self.data.get(data_id, [])
 
class TestNotifier(NotifierABC):
    def notify(self, data, meta):
        pass
An Actual Test

Now we can test get_data the service without any mocks.

**/tests/unit/test_service.py
def test_get_data_for_fake_id():
    # given
    test_repository = TestRepository()
    test_notifier = TestNotifier()
    service = Service(test_repository, test_notifier)
    data_id = "fake_data_id"
    user = "fake_user"
 
    # when
    returned_data = service.get_data(data_id, user)
 
    # then
    assert returned_data == []  # we return empty list for non-existing/fake data_id

Testing Endpoint

Testing endpoint should be done only if the service is tested and more logic or data manipulation is done in the endpoint.

Most likely, you will test the endpoint like this in the integration tests.

Integration testing

Integration testing is a type of testing that tests the integration of multiple units of code. In this case, we will test the endpoints.

Difference between unit and integration testing

Unit testing tests individual units of code. In this case, we tested the service.

Integration testing tests the integration of multiple units of code at once. In this case, we will test the endpoints.

Actual integration test example

You can test endpoints that uses services easily.

**/tests/integration/test_make_post_from_draft.py
def test_make_post_from_non_existing_draft(client):  # client from first article
    # given
    post_draft_id = "fake_post_draft_id_here"
 
    # when
    make_post_from_non_existing_draft_response = client.post(
        "/make_post_from_draft",
        json={"post_draft_id": post_draft_id},
    )
 
    # then
    assert make_post_from_non_existing_draft_response.status_code == 404

Next

That's it! Now you can use this as a base for your extended testing needs with JSON API.

You can go to the following articles about Flask JSON API series.

This article is a part of Flask JSON API series.

READ

Latest readings

  • Readings are sites which will help you with detailed

  • information about given topic. Read latest ones from Learn.

AI

06-03-2026

Local Voice Assistant with Ollama
  • Build your own local voice assistant powered by Ollama.

AI

06-03-2026

AI YouTube Thumbnail Generator
  • Generate YouTube thumbnails with FastAPI and Ollama.

Architecture

05-09-2024

Graph DB usage comparison
  • Compare Neo4j and Tigergraph databases, which is easier to work with, etc.