Last active
March 6, 2023 18:22
-
-
Save abadger/226de9d9d08eb329ca71bab010435a1a to your computer and use it in GitHub Desktop.
How to write a unittest that ignores a decorator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# let's say we have a function "testing" which is decorated by "my_decorator" | |
import functools | |
def my_decorator(func): | |
@functools.wraps(func) | |
def wrapped(*args, **kwargs): | |
print("In real decorator") | |
print(func.__name__) | |
print(args) | |
print(kwargs) | |
return func(*args, **kwargs) | |
# Python2 and Python3 < 3.2 compatibility | |
if not hasattr(wrapped, "__wrapped__"): | |
wrapped.__wrapped__ = func | |
return wrapped | |
@my_decorator | |
def testing(msg): | |
print("We got: %s" % msg) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# For some reason (maybe the decorator accesses resources which aren't | |
# available in the test environment), when we unittest, we want to test | |
# the function without its decoration. | |
import functools | |
try: | |
from unittest import mock | |
except: | |
import mock | |
import pytest | |
import decorate | |
# We want to replace the real decorator with one that doesn't do anything. | |
# In a real test, this decorator might do something like record how many | |
# times it was called. | |
def mock_decorator(func): | |
@functools.wraps(func) | |
def wrapped(*args, **kwargs): | |
print("In mock decorator") | |
return func(*args, **kwargs) | |
return wrapped | |
# This way does not work because the decorator is applied when "testing" | |
# Is defined. So monkeypatching the decorator at this point is too late. | |
# The Python interpreter has already defined "testing" when it imported | |
# "decorate" on line 14. | |
@pytest.fixture | |
def m_dec(monkeypatch): | |
monkeypatch.setattr(decorate, "my_decorator", mock_decorator) | |
def test_monkeypatching_decorator(capsys, m_dec): | |
print("In test function") | |
decorate.testing("Tested") | |
stdout = capsys.readouterr().out | |
assert "In real decorator" not in stdout | |
assert "In mock decorator" in stdout | |
# This way will work because it finds the original function (The undecorated "testing") | |
# and wraps it in our decorator that does nothing instead of the real decorator. | |
@pytest.fixture | |
def rewrap(monkeypatch): | |
original_func = decorate.testing.__wrapped__ | |
print(id(decorate.testing)) | |
print(original_func) | |
print(original_func.__name__) | |
print(id(original_func)) | |
monkeypatch.setattr(decorate, "testing", mock_decorator(original_func)) | |
print(id(decorate.testing)) | |
def test_rewrap_decorated_function(capsys, rewrap): | |
decorate.testing("Tested") | |
stdout = capsys.readouterr().out | |
assert "In real decorator" not in stdout | |
assert "In mock decorator" in stdout |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment