UrbanoJVR
UrbanoJVR

Reputation: 1297

Inconsistent behavior between Flask real session storage and test_request_context during tests

I’m using Flask’s session storage to temporarily save a list of dataclass objects. Here’s an example of the Transaction class and my TransactionMemoryRepository:

@dataclass
class Transaction:

    transaction_date: datetime.date
    amount: decimal.Decimal
    concept: str

The repository has methods to save and retrieve transactions from the session:

from flask import session

class TransactionMemoryRepository:

    @staticmethod
    def save_transactions(transactions: list[Transaction]):
        session['transactions'] = transactions

    @staticmethod
    def get_transactions() -> list[Transaction]:
        tmp_transactions = session.get('transactions', [])
        transactions = []

        for tmp_transaction in tmp_transactions:
            transaction = Transaction(
                transaction_date=datetime.strptime(tmp_transaction['transaction_date'], '%a, %d %b %Y %H:%M:%S %Z'),
                amount=Decimal(tmp_transaction['amount']),
                concept=tmp_transaction['concept'],
                category=tmp_transaction.get('category'),
                id=tmp_transaction.get('id')
            )
            transactions.append(transaction)

        return transactions

The issue is that in real execution, Flask’s session storage saves the list of Transaction objects as a list of dictionaries. This is why I need to map each dictionary back to a Transaction object when reading from the session.

However, during tests using test_request_context, the behavior is different: The session stores the objects as actual Transaction instances, which causes the read method to fail with the error:

TypeError: 'Transaction' object is not subscriptable

Here’s my test setup using pytest:

    @pytest.fixture
    def flask_app():
        app = Flask(__name__)
        app.secret_key = "test_secret_key"
        return app

    @pytest.fixture
    def flask_request_context(flask_app):
        with flask_app.test_request_context():
            yield

Then, I use this fixture on my test:

        def test_save_and_get_transactions(self, flask_request_context):

            transactions = [
                Transaction(amount=Decimal(100), concept="Concept 1",
                            transaction_date=datetime.now()),
                Transaction(amount=Decimal(200), concept="Concept 2",
                            transaction_date=datetime.now())
        ]

            TransactionMemoryRepository.save_transactions(transactions)

            result = TransactionMemoryRepository.get_transactions()

            #asserts ...

The issue: In production, session['transactions'] becomes a list of dictionaries, but during tests, it stores actual Transaction objects. As a result, the get_transactions() method works fine in the real application but fails in tests, because I'm accessing attributes as if they were dictionaries.

Question:

Why is there a difference between how Flask's session behaves during real execution and in tests using test_request_context? How can I ensure the session behaves the same way in both environments so that my tests reflect the actual behavior?

The temporary solution, for now, is mapping to a dict when save but this is a workaround.

I’m attaching two images to show the debugging results. You can see that during normal execution, the session returns a Transaction object with properly typed attributes, while during tests, it returns a dict.

REAL execution is reading DICT instances

TEST execution is reading real DATACLASS Transaction instances

Upvotes: 0

Views: 20

Answers (1)

Szymon Roziewski
Szymon Roziewski

Reputation: 1129

In production, the session payload is serialized and deserialized (which converts objects into primitives like dictionaries). During tests, no serialization/deserialization takes place, and objects are stored directly in memory.

Update the repository methods (save_transactions and get_transactions) as follows:

from decimal import Decimal
from datetime import datetime
from flask import session

class TransactionMemoryRepository:

@staticmethod
def save_transactions(transactions: list['Transaction']):
    # Convert Transaction instances to dictionaries before saving to session
    session['transactions'] = [t.__dict__ for t in transactions]

@staticmethod
def get_transactions() -> list['Transaction']:
    tmp_transactions = session.get('transactions', [])
    transactions = []

    for tmp_transaction in tmp_transactions:
        transaction = Transaction(
            transaction_date=datetime.strptime(tmp_transaction['transaction_date'], '%Y-%m-%d'),
            amount=Decimal(tmp_transaction['amount']),
            concept=tmp_transaction['concept']
        )
        transactions.append(transaction)

    return transactions

In both production and test environments, a session will now always contain serialized structures (dictionary lists) to ensure consistency. And to test

def test_save_and_get_transactions(self, flask_request_context):
# Arrange
transactions = [
    Transaction(amount=Decimal(100), concept="Concept 1", transaction_date=datetime.now()),
    Transaction(amount=Decimal(200), concept="Concept 2", transaction_date=datetime.now())
]

# Act
TransactionMemoryRepository.save_transactions(transactions)
result = TransactionMemoryRepository.get_transactions()

# Assert
assert len(result) == 2
assert result[0].amount == Decimal(100)
assert result[0].concept == "Concept 1"

Upvotes: 0

Related Questions