1
+ import sys
2
+ import types
3
+ from contextlib import contextmanager
4
+ from unittest .mock import MagicMock , Mock , patch
5
+
1
6
import pytest
2
7
3
8
# we have to import the pytest plugin fixtures here,
20
25
)
21
26
22
27
28
+ SENTINEL = object ()
29
+
30
+
23
31
@pytest .fixture (scope = 'session' , autouse = True )
24
32
def setup_default_app_trap ():
25
33
from celery ._state import set_default_app
@@ -31,6 +39,117 @@ def app(celery_app):
31
39
return celery_app
32
40
33
41
42
+ @contextmanager
43
+ def module_context_manager (* names ):
44
+ """Mock one or modules such that every attribute is a :class:`Mock`."""
45
+ yield from _module (* names )
46
+
47
+
48
+ def _module (* names ):
49
+ prev = {}
50
+
51
+ class MockModule (types .ModuleType ):
52
+
53
+ def __getattr__ (self , attr ):
54
+ setattr (self , attr , Mock ())
55
+ return types .ModuleType .__getattribute__ (self , attr )
56
+
57
+ mods = []
58
+ for name in names :
59
+ try :
60
+ prev [name ] = sys .modules [name ]
61
+ except KeyError :
62
+ pass
63
+ mod = sys .modules [name ] = MockModule (name )
64
+ mods .append (mod )
65
+ try :
66
+ yield mods
67
+ finally :
68
+ for name in names :
69
+ try :
70
+ sys .modules [name ] = prev [name ]
71
+ except KeyError :
72
+ try :
73
+ del sys .modules [name ]
74
+ except KeyError :
75
+ pass
76
+
77
+
78
+ class _patching :
79
+
80
+ def __init__ (self , monkeypatch , request ):
81
+ self .monkeypatch = monkeypatch
82
+ self .request = request
83
+
84
+ def __getattr__ (self , name ):
85
+ return getattr (self .monkeypatch , name )
86
+
87
+ def __call__ (self , path , value = SENTINEL , name = None ,
88
+ new = MagicMock , ** kwargs ):
89
+ value = self ._value_or_mock (value , new , name , path , ** kwargs )
90
+ self .monkeypatch .setattr (path , value )
91
+ return value
92
+
93
+ def object (self , target , attribute , * args , ** kwargs ):
94
+ return _wrap_context (
95
+ patch .object (target , attribute , * args , ** kwargs ),
96
+ self .request )
97
+
98
+ def _value_or_mock (self , value , new , name , path , ** kwargs ):
99
+ if value is SENTINEL :
100
+ value = new (name = name or path .rpartition ('.' )[2 ])
101
+ for k , v in kwargs .items ():
102
+ setattr (value , k , v )
103
+ return value
104
+
105
+ def setattr (self , target , name = SENTINEL , value = SENTINEL , ** kwargs ):
106
+ # alias to __call__ with the interface of pytest.monkeypatch.setattr
107
+ if value is SENTINEL :
108
+ value , name = name , None
109
+ return self (target , value , name = name )
110
+
111
+ def setitem (self , dic , name , value = SENTINEL , new = MagicMock , ** kwargs ):
112
+ # same as pytest.monkeypatch.setattr but default value is MagicMock
113
+ value = self ._value_or_mock (value , new , name , dic , ** kwargs )
114
+ self .monkeypatch .setitem (dic , name , value )
115
+ return value
116
+
117
+ def modules (self , * mods ):
118
+ modules = []
119
+ for mod in mods :
120
+ mod = mod .split ('.' )
121
+ modules .extend (reversed ([
122
+ '.' .join (mod [:- i ] if i else mod ) for i in range (len (mod ))
123
+ ]))
124
+ modules = sorted (set (modules ))
125
+ return _wrap_context (module_context_manager (* modules ), self .request )
126
+
127
+
128
+ def _wrap_context (context , request ):
129
+ ret = context .__enter__ ()
130
+
131
+ def fin ():
132
+ context .__exit__ (* sys .exc_info ())
133
+ request .addfinalizer (fin )
134
+ return ret
135
+
136
+
137
+ @pytest .fixture ()
138
+ def patching (monkeypatch , request ):
139
+ """Monkeypath.setattr shortcut.
140
+ Example:
141
+ .. code-block:: python
142
+ >>> def test_foo(patching):
143
+ >>> # execv value here will be mock.MagicMock by default.
144
+ >>> execv = patching('os.execv')
145
+ >>> patching('sys.platform', 'darwin') # set concrete value
146
+ >>> patching.setenv('DJANGO_SETTINGS_MODULE', 'x.settings')
147
+ >>> # val will be of type mock.MagicMock by default
148
+ >>> val = patching.setitem('path.to.dict', 'KEY')
149
+ """
150
+ return _patching (monkeypatch , request )
151
+
152
+
34
153
@pytest .fixture (autouse = True )
35
154
def test_cases_shortcuts (request , app , patching ):
36
155
if request .instance :
0 commit comments