0

I want to do a unittest with pytest for this method:

class UserService:
    @classmethod
    async def get_user_by_login(cls, session: SessionDep, login: str) -> Optional[User]:
        sql = select(User).where(User.login == login)
        result = await session.exec(sql)
        return result.first()

I have already this test:

@pytest.mark.asyncio
async def test_get_user_by_login(mocker):
    # Arrange
    mock_exec_result = AsyncMock(return_value=None)
    mock_session = mocker.AsyncMock()
    mock_session.exec.return_value = mock_exec_result
    sql_request = select(User).where(User.login == LOGIN)

    # Act
    await UserService.get_user_by_login(mock_session, LOGIN)

    # Assert
    mock_session.exec.assert_called_once_with(sql_request)
    mock_session.exec.assert_called_once()
    mock_exec_result.first.assert_called_once_with()

But I want to test sql_request variable with assert_called_once_with and it obviously fail because these are not the same object instance...

See the logs:

        # Assert
>       mock_session.exec.assert_called_once_with(sql_request)
E       AssertionError: expected call not found.
E       Expected: exec(<sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478A0EFC0>)
E         Actual: exec(<sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478884B00>)
E       
E       pytest introspection follows:
E       
E       Args:
E       assert (<sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478884B00>,) == (<sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478A0EFC0>,)
E         
E         At index 0 diff: <sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478884B00> != <sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478A0EFC0>
E         
E         Full diff:
E           (
E         -     <sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478A0EFC0>,
E         ?                                                                               ^ ---
E         +     <sqlmodel.sql._expression_select_cls.SelectOfScalar object at 0x0000028478884B00>,
E         ?                                                                               ^^^^
E           )
1
  • I wouldn't unit test that; it seems very coupled to the details of the persistence layer, and you're mocking what you don't own. But in general you can create objects with an __eq__ method and use those with assert_called_once_with to apply whatever logic you want to deciding if the real value is correct - stackoverflow.com/a/64973325/3001761. Commented Jun 7 at 10:49

1 Answer 1

1

You are not supposed to compare the instances of SQL expressions. You need to compare the SQL query itself. The parameter which was passed to the mock_session.exec method is stored by the mock. You can extract the arguments of the coroutine mock and compare its value to your expected value.

from sqlalchemy.dialects import postgresql

sql_expression = mock_session.exec.await_args[0][0]
assert str(sql_expression.compile(dialect=postgresql.dialect())) == str(select(User).where(User.login == LOGIN).compile(dialect=postgresql.dialect()))

If the SQL expression is changed in future, the test will break. You must provide the appropriate dialect to compile the SQL expression according to the database you use.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.