Skip to content

Commit e488e56

Browse files
committed
Tests and minor bug fixes for resolve_target.
1 parent b4c1815 commit e488e56

File tree

6 files changed

+86
-5
lines changed

6 files changed

+86
-5
lines changed

config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"./tests/__init__.py": "tests/__init__.py",
66
"./tests/test_asyncmock.py": "tests/test_asyncmock.py",
77
"./tests/test_mock.py": "tests/test_mock.py",
8-
"./tests/test_patch.py": "tests/test_patch.py"
8+
"./tests/test_patch.py": "tests/test_patch.py",
9+
"./tests/test_resolve_target.py": "tests/test_resolve_target.py",
10+
"./tests/a_package/__init__.py": "tests/a_package/__init__.py",
11+
"./tests/a_package/a_module.py": "tests/a_package/a_module.py"
912
}
1013
}

tests/a_package/__init__.py

Whitespace-only changes.

tests/a_package/a_module.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
A dummy module for testing purposes.
3+
"""
4+
5+
import random
6+
7+
8+
class AClass:
9+
"""
10+
A dummy class for testing.
11+
"""
12+
13+
def __init__(self, a: int):
14+
self.a = a
15+
16+
def a_method(self, b):
17+
return self.a + b
18+
19+
def __repr__(self):
20+
return f"AClass(a={self.a})"
21+
22+
23+
def a_function(a, b):
24+
return a + b
25+
26+
27+
def another_function(a, b):
28+
return random.randint(a, b)

tests/test_patch.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
Tests the patch decorator/context manager.
33
"""
44

5+
import upytest
56
from umock import patch
67

78

9+
@upytest.skip("upytest does not support patching yet")
810
def test_patch_decorator():
911
"""
1012
Tests the patch decorator.

tests/test_resolve_target.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
Tests for the resolve_target function used to return a target Python object
3+
from a target string.
4+
"""
5+
6+
import upytest
7+
from umock import resolve_target
8+
9+
10+
def test_resolve_target_no_colon():
11+
"""
12+
The target path is a dotted string without a colon pointing to a specific
13+
object in the module.
14+
"""
15+
target = "tests.a_package.a_module.AClass"
16+
expected = "AClass"
17+
actual = resolve_target(target)
18+
assert actual.__name__ == expected
19+
20+
21+
def test_resolve_target_with_colon():
22+
"""
23+
The target path is a dotted string with a colon pointing to a specific
24+
object in the module.
25+
"""
26+
target = "tests.a_package.a_module:AClass.a_method"
27+
expected = "a_method"
28+
actual = resolve_target(target)
29+
assert actual.__name__ == expected
30+
31+
32+
def test_resolve_target_with_object():
33+
"""
34+
If the target is already an object, return it as is.
35+
"""
36+
target = "tests.a_package.a_module.AClass"
37+
expected = resolve_target(target)
38+
actual = resolve_target(expected)
39+
assert actual is expected
40+
41+
42+
def test_resolve_target_invalid_target():
43+
"""
44+
If the target path points to an invalid object, raise an AttributeError.
45+
"""
46+
target = "tests.a_package.a_module:AClass.not_a_method"
47+
with upytest.raises(AttributeError):
48+
resolve_target(target)

umock.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,15 +358,15 @@ def resolve_target(target):
358358
"Inspired by" pkgutil.resolve_name in the CPython standard library (but
359359
much simpler/naive).
360360
361-
Will raise an ImportError if the target module cannot be resolved or an
362-
AttributeError if the attribute cannot be found.
361+
Will raise an AttributeError if the target object cannot be resolved.
363362
"""
364363
if not isinstance(target, str):
365364
return target
366365
if ":" in target:
367366
# There is a colon - a one-step import is all that's needed.
368367
module_name, attributes = target.split(":")
369-
module = __import__(module_name)
368+
module_path = module_name.replace(".", "/")
369+
module = __import__(module_path)
370370
parts = attributes.split(".")
371371
else:
372372
# No colon - have to iterate to find the package boundary.
@@ -377,7 +377,7 @@ def resolve_target(target):
377377
while parts:
378378
# Traverse the parts of the target to find the package boundary.
379379
p = parts[0]
380-
new_module_name = f"{module_name}.{p}"
380+
new_module_name = f"{module_name}/{p}"
381381
try:
382382
module = __import__(new_module_name)
383383
parts.pop(0)

0 commit comments

Comments
 (0)