Python Mocking with unitest.mock 2: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 63: Line 63:
     assert str(e) == 'test'
     assert str(e) == 'test'
</syntaxhighlight>
</syntaxhighlight>
This is a simplistic approach, appropriate when we don't need flexible behavior depending on the arguments. For a more nuanced approach, see [[#Plug-in_Arbitrary_Behavior_with_Access_to_Invocation_Arguments|Arbitrary Behavior with Access to Invocation Arguments]] below.


==Plug-in Arbitrary Behavior with Access to Invocation Arguments==
==Plug-in Arbitrary Behavior with Access to Invocation Arguments==

Revision as of 23:55, 1 June 2023

Internal

Mocking a Method

The general approach is to replace at runtime the real method instance associated with the class instance to be tested with a Mock instance, configured to simulate various behaviors of the real method.

Assuming that our dependency to test with is SomeClass, and this class has a some_method(nuance: str) whose behavior we want to mock during testing, the initial implementation of the class and method could be:

 
class SomeClass:
    def __init__(self, color: str):
        self._color = color

    def some_method(self, nuance: str) -> str:
        return f'{nuance} {self._color}'.upper()

The normal behavior of the method some_method(nuance: str) is reflected by:

 
c = SomeClass('blue')
assert c.some_method('dark') == 'DARK BLUE'

c._color = 'red'
assert c.some_method('light') == 'LIGHT RED'

We can mock the behavior of the some_method(nuance: str) method in the following ways:

  • We can return a constant value, regardless of the arguments the method is invoked with.
  • We can raise an exception, regardless of the arguments the method is invoked with.
  • We can replace the behavior of the method with arbitrary logic, that takes into account the arguments the method is called with.

Simulating a Particular Return Value Irrespective of the Arguments it was Called With

Configure Mock() using the return_value argument of its constructor so no matter how the mocked method is invoked, it will always return a constant value:

c = SomeClass('blue')
c.some_method = Mock(return_value='something completely arbitrary')
assert c.some_method('argument does not matter') == 'something completely arbitrary'

Irrespective of how the method is invoked in testing, the calling code will always get the value configured with return_value on the mock.

This is a simplistic approach, appropriate when we don't need flexible behavior depending on the arguments. For a more nuanced approach, see Arbitrary Behavior with Access to Invocation Arguments below.

Simulating Throwing an Exception Irrespective of the Arguments it was Called With

Configure Mock() using the side_effect argument of its constructor, by providing an exception **instance**. Once configured as such, the mocked method will always throw exception, no matter how it is invoked.

c = SomeClass('blue')
c.some_method = Mock(side_effect=ValueError('test'))

try:
    c.some_method('argument does not matter')
except ValueError as e:
    assert str(e) == 'test'

This is a simplistic approach, appropriate when we don't need flexible behavior depending on the arguments. For a more nuanced approach, see Arbitrary Behavior with Access to Invocation Arguments below.

Plug-in Arbitrary Behavior with Access to Invocation Arguments