Home > front end >  Pytest - mocking a side_effect on mock's nested attribute function / method
Pytest - mocking a side_effect on mock's nested attribute function / method

Time:09-17

I have a fixture mocking an external library like so, using pytest-mock, which is a wrapper around unittest.mock.

# client.py

import Test as TestLibrary

class LibraryName():
   def get_client():
      return TestLibrary.Library()

# library_service.py

def using_library():
   '''
   Edited note: Library().attribute behind the scenes is set to
   self.attribute = Attribute() 
   so this may be affecting the mocking
   '''
   client = LibraryName.get_client()
   return client.attribute.method()

# conftest.py

@pytest.fixture
def library_client_mock(mocker):
    import Test as TestLibrary

    return mocker.patch.object(TestLibrary, 'Library')

# test_library_service.py

def test_library_method(library_client_mock):
   result = using_library()

I can mock a return value like so:

def test_library_method(library_client_mock):
   library_client_mock.return_value.attribute.return_value.method.return_value = "test"
   result = using_library()
   assert result == "test"

but I can't mock throwing an Exception with side_effect

def test_library_method(library_client_mock):
    library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
    library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
    attrs = { 'attribute.method.side_effect': TypeError }
    library_client_mock.configure_mock(**attrs) # doesn't work

    with pytest.raises(TypeError):   
        using_library() # fails assertion

what I missing here?

CodePudding user response:

These are the errors in your code:

  1. Change:

    library_client_mock.return_value.attribute.return_value.method.return_value = "test"
    

    To:

    library_client_mock.return_value.attribute.method.return_value = "test"
    
  2. Change:

    library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
    

    To:

    library_client_mock.return_value.attribute.method.side_effect = TypeError
    

Explanation

The .return_value must only be used for callable objects e.g. a function as documented:

return_value

Set this to configure the value returned by calling the mock:

>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'

Thus, you can use .return_value only for the following:

  • TestLibrary.Library()
  • TestLibrary.Library().attribute.method()

But not for:

  • TestLibrary.Library().attribute

Because .attribute is not a callable e.g. TestLibrary.Library().attribute().

Warning

The way you are patching Library is via its source location at Test.Library (or aliased as TestLibrary.Library). specifically via:

import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')

It works currently because the way you import and use it is via the root path.

# client.py
import Test as TestLibrary
...
    return TestLibrary.Library()
...

But if we change the way we imported that library and imported a local version to client.py:

# client.py
from Test import Library  # Instead of <import Test as TestLibrary>
...
    return Library()  # Instead of <TestLibrary.Library()>
...

It will now fail. Ideally, you should patch the specific name that is used by the system under test, which here is client.Library.

import client
return mocker.patch.object(client, 'Library')

Unless you are sure that all files that will use the library will import only the root and not a local version.

CodePudding user response:

@Niel Godfrey Ponciano set me on the right path with this syntax for the side_effect

library_client_mock.return_value.attribute.method.side_effect = TypeError

but it wasn't enough.

In

# conftest.py

@pytest.fixture
def library_client_mock(mocker):
    import Test as TestLibrary

    return mocker.patch.object(TestLibrary, 'Library')

I had to add an extra mock:

# conftest.py

@pytest.fixture
def library_client_mock(mocker):
    import Test as TestLibrary

    mock_library_client = mocker.patch.object(TestLibrary, 'Library')
    # option 1
    mock_attribute = Mock()
    # option 2, path to Library.attribute = Attribute()
    mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
    mock_library_client.attach_mock(mock_attribute, "attribute")
    return mock_library_client

and then both of the following statements worked as expected. Although I am not sure why return_value works out of the box without an attached mock, but side_effect does not.

# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"

# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError
  • Related