mock-alchemy icon indicating copy to clipboard operation
mock-alchemy copied to clipboard

db.query(User).filter(User.id = id).first() always return None

Open alexnguyen2201 opened this issue 2 years ago • 2 comments

Describe the bug It's fine if sfwafawfcwafawf but first:

db.query(User).filter(User.id = id).sfwafawfcwafawf()= 112

but first:

db.query(User).filter(User.id = id).first() = None

To Reproduce For example:

    def test_find_user():
        db = UnifiedAlchemyMagicMock()
        db.query.return_value.filter.return_value.first.return_value = 112
        UserRepository(db).find_user(id=1)
        assert 0

and

    def find_user(id: int) -> None:
        print("result of mock db:", db.query(User).filter(User.id = id).first())

Expected behavior If i set: db.query.return_value.filter.return_value.first.return_value = 112

So the result of db.query().filter().first() should be 112

Screenshots If applicable, add screenshots to help explain your problem. target user randon user() def is user following for another( Screen Shot 2022-06-04 at 16 11 04

  • OS: MacOS Monterey
  • Version 12.3.1

alexnguyen2201 avatar Jun 04 '22 09:06 alexnguyen2201

I have the same problem! OS: Kubuntu 22.04 Version: mock-alchemy==0.2.5

facundopadilla avatar Dec 09 '22 01:12 facundopadilla

I think I found the error but I am not sure if it is this one:

  • mock_alchemy/mocking.py in line 620:
for calls, result in sorted_mock_data:
    calls = [
        sqlalchemy_call(
            i,
            with_name=True,
            base_call=self.unify.get(i[0]) or Call,
        )
        for i in calls
    ]
    if all(c in previous_calls for c in calls):  # line 620
        return self.boundary[_mock_name](result, *args, **kwargs)

This check if the some call exists in previous_calls (created in line 590)

For example, in my case I have this:

  • Calls: [call.query(User), call.filter(User.username == "test"), call.first()]
  • Return value: [User(id=1, username="test", password="test")]

Code:

session = UnifiedAlchemyMock(
    data=[([call.query(User), call.filter(User.username == "test"), call.first()], [User(id=1, username="test", password="test")])]
)

When line 620 is to be executed, the previous_calls has this:

[
    call.filter(BinaryExpression(sql='users.username = :username_1', params={'username_1': 'test'})),
    call.query(<class 'project.models.users.User'>), 
    call.__len__(),
    call.__str__(),
    call.__len__()
]

The call first() is not added in previous_calls, then it fails.

I guess it must be because the first() is not a method of sqlalchemy as such, i.e. as a query, but it is a method of the same object that returns the filter and returns the first of the list as if it were a slicing (any_list[0])

# sqlalchemy/orm/query.py - line 2799
def first(self):
    if self._statement is not None:
        return self._iter().first()
    else:
        return self.limit(1)._iter().first()

facundopadilla avatar Dec 09 '22 01:12 facundopadilla