0

How to test the following decorator which calls 3rd party library?

import third_party_lib
import functools

class MyDecorator:
    def __init__(self, ....):
        self.third_party_lib = ThirdPartyLib(....) # will create a 3rd party instance

    def __call__(self, ...):
        def decorator(f):
            @functools.wraps(f)
            def wrap(*arg, **kwargs):
                result = f(*arg, **kwargs)
                # ....
                a = self.third_party_lib.send(value=result).get()
                # ....
                return result
            return wrap
        return decorator

I need to create an unit test to assert third_party_lib.send() is called if a function is decorated by the decorator. And ideally, also assure the result of the test function is passed to the function.

decorator = MyDecorator(....)

@decorator(....)
def test_func():
    ret = ...
    return ret # ret should be passed to `third_party_lib.send()`
ca9163d9
  • 27,283
  • 64
  • 210
  • 413

1 Answers1

1

If you want to verify that the thirdparty function is called correctly, you can mock it and check that the mock is called with the correct parameters. As the ThirdPartyLib intialization shall also be mocked, as mentioned in the comments, you hav to make sure that the docorator is constructed after the mock has been set, for example by constructing it inside the test:

from unittest import mock

@mock.patch('third_party_lib.ThirdPartyLib')
def test_my_decorator(mocked_lib):
    decorator = MyDecorator()

    @decorator()
    def example_func():
        return 42

    example_func()
    mocked_lib.return_value.send.assert_called_once_with(value=42)

If you need the decorated function in more tests, you can wrap it in a function instead:

def decorated_func():
    decorator = MyDecorator()

    @decorator()
    def example_func():
        return 42

    return example_func

@mock.patch('third_party_lib.ThirdPartyLib')
def test_my_decorator(mocked_lib):
    decorated_func()()
    mocked_lib.return_value.send.assert_called_once_with(value=42)
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • It sounds like what I need. Is the `mocked_send` from the built-in `unittest.mock`? – ca9163d9 Sep 28 '20 at 18:01
  • Yes, it's a positional argument introduced by `unittest.mock.patch`, so the name is not relevant (I added the import to clarify this). – MrBean Bremen Sep 28 '20 at 18:03
  • The `__init__()` will create an instance of the `third_part_lib` class. How to prevent/mock it when executing `decorator = MyDecorator(...)`? It will make some network calls which may not be available during testing. – ca9163d9 Sep 28 '20 at 20:40
  • Ok, I adapted the answer for that. – MrBean Bremen Sep 29 '20 at 04:40
  • Will `@mock.patch('third_party_lib.ThirdPartyLib')` will prevent the `__init__()` of `MyDecorator` from creating an instance of `ThirdPartyLib`? I was thinking to refactory the class to use dependence injection approach to inject an instance of `ThirdPartyLib` (which is common for Java/C# code). Maybe it's not necessary for `mock.path()`? – ca9163d9 Sep 29 '20 at 05:05
  • It will create an instance of the mock (which you can access by `mocked_lib.return_value`), so this will be sufficient. – MrBean Bremen Sep 29 '20 at 05:06
  • Great. So I don't need to refactory the code the Java/C# way to enable mock. So I will not need to ask the question of https://stackoverflow.com/questions/64109791/mock-class-instance-created-in-init?noredirect=1#comment113373942_64109791 – ca9163d9 Sep 29 '20 at 05:09
  • I tried the code. However, `@mock.patch('KafkaProducer')` got the error of `ValueError: not enough values to unpack (expected 2, got 1)`? – ca9163d9 Sep 29 '20 at 05:36
  • `KafkaProducer` is from `kafka` originally. I've undeleted and updated the question https://stackoverflow.com/questions/64109791/mock-class-instance-created-in-init?noredirect=1#comment113373942_64109791 – ca9163d9 Sep 29 '20 at 06:04