In modern computing, it’s a common belief that computers execute tasks in parallel, effortlessly juggling multiple operations at once.

Asynchronous, or async, allows a program to efficiently execute its tasks (executing other tasks one while is waiting). This is also called concurrency.

This is where Pytest Asyncio steps in.

pytest-asyncio simplifies handling event loops, managing async fixtures, and bridges the gap between async programming and thorough testing.

Async — Use Cases

Non-Blocking I/O (Network)

I/O operations, for example

  • File images or downloads

  • R/W to local disk or cloud storage

  • R/W to a database

All these tasks have network or server overhead (i.e. their speed of execution depends on the speed of the network or server).

Calling External APIs or Services

External API calls take longer and waste valuable execution time so it’s good design practice that your program does something useful rather than just wait around.

Audio / Image Processing and Computer Vision

It’s good to write optimised async code that can perform other tasks in the meanwhile.

Machine Learning / Model Training

During model training, performing other critical or non-critical tasks like file images, status changes, polling, R/W to db etc. may be possible.

Server Responses And Chatbots

Server responses and chatbots may need to respond to several requests at the same time.

Data ETL or ELT Operations

Data syncing or ELT (Extract, Load, Transform) operations can be time-consuming and can often be executed asynchronously.

Testing Async Functions (Pytest-Asyncio)

You can write your tests as you would normally and use the @pytest.mark.asyncio marker to tell Pytest that the function is async along with the async keyword before defining your test functions.

# Basic Async test function  
@pytest.mark.asyncio  
async def test_async_add():  
    result = await async_add(1, 2)  
    assert result == 3

Async Fixtures

Very similar. You use the @pytest_asyncio.fixture() marker and define the fixture as you would normally.

# Async fixtures  
@pytest_asyncio.fixture  
async def loaded_data():  
    await asyncio.sleep(1)  # Simulate loading data asynchronously  
    return {"key": "value"}

Async Mocking Using AsyncMock

# Test with Mocked API call (Recommended when interacting with external services)
@pytest.mark.asyncio
async def test_get_cat_fact_mocked(mocker):
    # Mock the get_cat_fact method of CatFact
    mock_response = {"status": 200, "result": {"data": "Cats are awesome!"}}
    mocker.patch.object(CatFact, "get_cat_fact", AsyncMock(return_value=mock_response))
 
    cat_fact_instance = CatFact()
    result = await cat_fact_instance.get_cat_fact()
 
    assert result["status"] == 200
    assert "data" in result["result"]
    assert result["result"]["data"] == "Cats are awesome!"