Mocking Session

The mocking of the previous file query is working fine. But it’s necessary to check that the data is also saved in the database.

The project uses SQLAlchemy as an ORM to access the database. To access the database with SQLAlchemy, a Session instance is needed. A Session handles the connection to the database and database transactions. With a Session instance, it’s possible to perform database queries by calling query(). Transactions can be committed by calling commit(). Once commit is called, all data is written to the database.

To verify that data is saved to the database, it must be asserted that session.commit() was called once. The other option would be to query the database and check if the data was stored correctly. But it’s not necessary to check if SQLAlchemy persisted the data correctly in the database. It’s doubtful that SQLAlchemy is not working correctly. So it just has to be verified that session.commit() was called once. For this, the Session object can be mocked, so a call to commit() can be quickly asserted. Furthermore, nothing is saved in the database, and the test is independent from the database.

The Session object is constructed in create_maya_scene:

    [...]
    
    session = companyname_db.base.Session.object_session(parent)
    
    [...]

Constructing objects for external dependencies in code that contains logic is an antipattern. It’s possible to mock Session using mock.patch, but this is not a good idea. See this section on Dependency Injection.

It would be way better to pass a Session to the function as a parameter. But adding a parameter can break the whole code, and it’s not a good idea to touch code outside create_maya_scene because no tests exist for this code. But there is a trick: When a Session keyword argument is added, the default value for the keyword argument can be None. If no value is passed, the Session can still be created inside create_maya_scene, so no existing behavior changes. In the test function, a mock can be passed as a Session parameter. This is safe to do because it’s only adding a parameter and one simple if statement. This is pretty safe to do and ok to make the function testable:

def create_maya_scene(parent, name='main', uuid=None, user=None, filetype=None, description='', version=None,
                      current=None, variant='', session=None, **kwargs):
                      
     [...]
     
    if not session:
        session = companyname_db.base.Session.object_session(parent)
    
    [...]
        

In the test function a mock is passed and the call to commit() is asserted.

@mock.patch("[...]maya.api.filemanager.query_previous_files")
def test_create_maya_scene(mock_query_previous_files):
    shot = Shot('myAwesomeShot')
    user = User('Jan Honsbrok', 'janhon')
    current = File(shot=shot)
    mock_query_previous_files.return_value = [File(shot=shot, name='test', filetype='filetype', version=0)]
    mock_session = mock.MagicMock()

    create_maya_scene(shot, user=user, current=current, session=mock_session)

    mock_session.commit.assert_called_once()
Last modified August 19, 2020