Completed
Pull Request — master (#15)
by Ramon
03:34
created

TestInject.test_inject_async_function()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
import asyncio
2
from abc import ABC, abstractmethod
3
from pathlib import Path
4
from typing import Type, List, Callable, Any, Awaitable
5
from unittest import TestCase
6
from jacked import inject, injectable, Injectable
7
from jacked._container import Container, DEFAULT_CONTAINER
8
from jacked._discover import discover
9
from jacked._exceptions import InvalidUsageError, InjectionError
10
from jacked._inject import inject_here
11
from test_resources.color import Color
12
13
14
CUSTOM_CONTAINER = Container()
15
16
17
class Animal(ABC):
18
    @abstractmethod
19
    def sound(self):
20
        raise NotImplementedError
21
22
23
@injectable(singleton=True)
24
class Dog(Animal):
25
    def sound(self):
26
        return 'bark'
27
28
29
@injectable  # No parentheses.
30
class Cat(Animal):
31
    def sound(self):
32
        return 'meow'
33
34
35
@injectable(priority=42)
36
class Bird(Animal):
37
    def sound(self):
38
        return 'tweet'
39
40
41
@injectable(name='Elephant', meta={'size': 'enormous', 'name': 'overridden'})
42
class Mouse(Animal):
43
    def sound(self):
44
        return 'peep'
45
46
47
@injectable(container=CUSTOM_CONTAINER)
48
class Goat(Animal):
49
    def sound(self):
50
        return 'meh'
51
52
53
# The same object injected in two different contains under different names.
54
@injectable()
55
@injectable(container=CUSTOM_CONTAINER, name='Kip')
56
class Chicken(Animal):
57
    def sound(self):
58
        return 'tok'
59
60
61
@injectable()
62
def func_str_str(x: str) -> str:
63
    return x.upper()
64
65
66
@injectable()
67
def func_int_str(x: int) -> str:
68
    return str(x)
69
70
71
@injectable()
72
def func_empty_float() -> float:
73
    return 42.0
74
75
76
@injectable()
77
def func_list_empty(x: list):
78
    return x + [1, 2, 3]
79
80
81
@injectable
82
def func_list_of_str_empty(x: List[str]):
83
    return x + ['42']
84
85
86
@injectable()
87
def func_cat_str(cat: Cat) -> str:
88
    return cat.sound()
89
90
91
@injectable()
92
async def async_func_str_str(x: int) -> str:
93
    return '42'
94
95
96
class TestInject(TestCase):
97
    @inject()
98
    def test_simple_injection(self, cat: Cat):
99
        self.assertEqual('meow', cat.sound())
100
101
    @inject()
102
    def test_singleton(self, dog1: Dog, dog2: Dog, cat1: Cat, cat2: Cat):
103
        self.assertEqual(dog1, dog2)
104
        self.assertNotEqual(cat1, cat2)
105
106
    @inject()
107
    def test_inject_prio(self, animal_type: Type[Animal]):
108
        self.assertEqual(Bird, animal_type)
109
110
    @inject()
111
    def test_inject_prio_list(self, animal_types: List[Type[Animal]]):
112
        self.assertEqual(Bird, animal_types[0])
113
114
    @inject()
115
    def test_injection_name(self, mouse: Mouse):
116
        self.assertEqual('Elephant', mouse.__meta__.name)
117
        self.assertEqual('enormous', mouse.__meta__.size)
118
119
    @inject
120
    def test_simple_injection_without_parentheses(self, cat: Cat):
121
        self.assertEqual('meow', cat.sound())
122
123
    @inject(container=CUSTOM_CONTAINER)
124
    def test_injection_with_different_container(self, animal: Animal):
125
        self.assertEqual('meh', animal.sound())
126
127
    def test_inject_here(self):
128
        cat = inject_here(Cat)
129
        self.assertEqual('meow', cat.sound())
130
131
    def test_register_on_the_fly(self):
132
133
        class SomeBaseClass:
134
            pass
135
136
        class SomeDerivedClass(SomeBaseClass):
137
            pass
138
139
        injectable_ = Injectable(
140
            subject=SomeDerivedClass,
141
            priority=0,
142
            singleton=False,
143
            meta={'name': SomeDerivedClass.__name__}
144
        )
145
146
        DEFAULT_CONTAINER.register(injectable_)
147
        injected = inject_here(SomeBaseClass)
148
149
        self.assertTrue(isinstance(injected, SomeBaseClass))
150
151
    def test_set_instance(self):
152
153
        @injectable(singleton=True)
154
        class C:
155
            pass
156
157
        inst = C()
158
        DEFAULT_CONTAINER.set_instance(C, inst)
159
        self.assertEqual(inst, inject_here(C))
160
161
    def test_injection_with_multiple_containers(self):
162
163
        @inject(container=CUSTOM_CONTAINER)
164
        def func1(chicken: Chicken):
165
            self.assertEqual('Kip', chicken.__meta__['name'])
166
167
        @inject()
168
        def func2(chicken: Chicken):
169
            self.assertEqual('Chicken', chicken.__meta__['name'])
170
171
        func1()
172
        func2()
173
174
    @inject()
175
    def test_inject_multiple(self, cat1: Cat, cat2: Cat):
176
        self.assertEqual('meow', cat1.sound())
177
        self.assertEqual('meow', cat2.sound())
178
        self.assertNotEqual(cat1, cat2)
179
180
    @inject()
181
    def test_inject_class(self, t: Type[Cat]):
182
        self.assertEqual(Cat, t)
183
184
    @inject()
185
    def test_inject_list(self, animals: List[Animal]):
186
        sounds = set([animal.sound() for animal in animals])
187
        self.assertTrue('bark' in sounds)
188
        self.assertTrue('meow' in sounds)
189
        self.assertTrue('tweet' in sounds)
190
        self.assertTrue('peep' in sounds)
191
        self.assertTrue('meh' not in sounds)  # Different container.
192
193
    @inject()
194
    def test_inject_list_of_classes(self, animals: List[Type[Animal]]):
195
        sounds = set([animal().sound() for animal in animals])
196
        self.assertTrue('bark' in sounds)
197
        self.assertTrue('meow' in sounds)
198
        self.assertTrue('tweet' in sounds)
199
200
    def test_inject_with_default(self):
201
202
        class NotInjectable:
203
            pass
204
205
        @inject()
206
        def _func(cat: Cat, obj: NotInjectable = 42):
207
            self.assertEqual('meow', cat.sound())
208
            self.assertEqual(42, obj)
209
210
        @inject()
211
        def _func2(animal: Cat = Dog()):
212
            # Dog is a default value but should be overridden by the injection.
213
            self.assertEqual('meow', animal.sound())
214
215
        _func()
216
        _func2()
217
218
    def test_inject_with_given_values(self):
219
220
        @inject()
221
        def _func(animal: Cat, another_animal: Bird):
222
            self.assertEqual('bark', animal.sound())
223
            self.assertEqual('tweet', another_animal.sound())
224
225
        class C:
226
            @inject()
227
            def method(self, animal: Cat, another_animal: Bird):
228
                outer_self.assertEqual('bark', animal.sound())
229
                outer_self.assertEqual('tweet', another_animal.sound())
230
231
        outer_self = self
232
233
        _func(animal=Dog())
234
        _func(Dog())
235
236
        C().method(animal=Dog())
237
        C().method(Dog())
238
239
    def test_inject_on_class(self):
240
        with self.assertRaises(InvalidUsageError):
241
            @inject()
242
            class C:
243
                pass
244
245
    def test_inject_function(self):
246
247
        @inject()
248
        def _func1(callable_: Callable[[int], str]):
249
            self.assertEqual(func_int_str, callable_)
250
251
        @inject()
252
        def _func2(callables: List[Callable[[str], str]]):
253
            self.assertEqual(1, len(callables))
254
            self.assertEqual('HELLO', callables[0]('hello'))
255
256
        @inject()
257
        def _func3(callable_: Callable[[], float]):
258
            self.assertEqual(func_empty_float, callable_)
259
260
        @inject()
261
        def _func4(callables: List[Callable[[Any], str]]):
262
            self.assertEqual(3, len(callables))
263
            self.assertTrue(func_int_str in callables)
264
            self.assertTrue(func_str_str in callables)
265
266
        @inject()
267
        def _func5(callables: List[Callable[[str], Any]]):
268
            self.assertEqual(1, len(callables))
269
            self.assertEqual(func_str_str, callables[0])
270
271
        @inject()
272
        def _func6(callable: Callable[[Animal], str]):
273
            self.assertEqual(func_cat_str, callable)
274
275
        @inject()
276
        def _func7(callables: List[Callable[[list], None]]):
277
            self.assertTrue(func_list_empty in callables)
278
            self.assertTrue(func_list_of_str_empty in callables)
279
280
        @inject()
281
        def _func8(callables: List[Callable[[List[str]], None]]):
282
            self.assertTrue(func_list_empty not in callables)
283
            self.assertTrue(func_list_of_str_empty in callables)
284
285
        _func1()
286
        _func2()
287
        _func3()
288
        _func4()
289
        _func5()
290
        _func6()
291
        _func7()
292
        _func8()
293
294
    def test_inject_method(self):
295
296
        local_container = Container()
297
298
        class SomeLocalClass:
299
            @injectable(container=local_container)
300
            def m(self, x: int) -> str:
301
                return '42'
302
303
        @inject(container=local_container)
304
        def func(method: Callable[[Any, int], str]):
305
            self.assertEqual(SomeLocalClass.m, method)
306
307
        func()
308
309
    def test_inject_into_async_function(self):
310
311
        @inject()
312
        async def func(animal: Cat):
313
            self.assertEqual('meow', animal.sound())
314
315
        asyncio.get_event_loop().run_until_complete(func())
316
317
    @inject()
318
    def test_inject_async_function(self, f1: Callable[[int], Awaitable[str]]):
319
        self.assertEqual(async_func_str_str, f1)
320
321
        # TODO test also with typing.Coroutine
322
323
    def test_inject_fail(self):
324
325
        class NotInjectable:
326
            pass
327
328
        @inject()
329
        def _func(obj: NotInjectable):
330
            pass
331
332
        with self.assertRaises(InjectionError):
333
            _func()
334
335
        try:
336
            _func()
337
        except InjectionError as err:
338
            self.assertEqual('obj', err.subject.name)
339
340
    def test_inject_with_discovery(self):
341
342
        @inject()
343
        def get_all_colors(colors: List[Color]):
344
            color_names = set([color.name() for color in colors])
345
            self.assertEqual(3, len(color_names))
346
            self.assertTrue('RED' in color_names)
347
            self.assertTrue('GREEN' in color_names)
348
            self.assertTrue('BLUE' in color_names)
349
350
        p = Path(__file__).parent.parent.joinpath('test_resources/injectables')
351
        discoveries = discover(str(p))
352
        module_names = [module.__name__ for module in discoveries]
353
354
        self.assertTrue('red' in module_names)
355
        self.assertTrue('green' in module_names)
356
        self.assertTrue('blue' in module_names)
357
358
        get_all_colors()
359