Skip to content

Commit 3300cf5

Browse files
author
Janos Tolgyesi
committed
Add tests
1 parent 2301599 commit 3300cf5

File tree

5 files changed

+151
-18
lines changed

5 files changed

+151
-18
lines changed

dynamodb_mapping/dynamodb_mapping.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from collections.abc import ValuesView, ItemsView, KeysView, MutableMapping
1111
from decimal import Decimal
1212
import logging
13+
import warnings
1314

1415
import boto3
1516
from boto3.dynamodb.types import Binary
@@ -148,7 +149,7 @@ def __contains__(self, value: object) -> bool:
148149
return False
149150

150151
def __iter__(self) -> Iterator:
151-
return self._mapping.scan()
152+
yield from self._mapping.scan()
152153

153154

154155
class DynamoDBItemsView(ItemsView):
@@ -460,9 +461,9 @@ def modify_item(
460461
if remove_expression_parts:
461462
update_expression_parts.append("remove " + ", ".join(remove_expression_parts))
462463
if not update_expression_parts:
463-
logger.warning(
464-
"No update expression was created by modify_item: modifications mapping is empty?"
465-
)
464+
warning_msg = "No update expression was created by modify_item: modifications mapping is empty?"
465+
warnings.warn(warning_msg, UserWarning)
466+
logger.warning(warning_msg)
466467
return
467468
update_expression = " ".join(update_expression_parts)
468469
logger.debug(

requirements_dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Sphinx
99
twine~=4.0.2
1010

1111
pytest~=7.4.0
12+
pytest-mock~=3.11.0
1213
black~=23.7.0
1314

1415
# Stubs

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ exclude = docs
1919
max-line-length = 100
2020

2121
[tool:pytest]
22-
collect_ignore = ['setup.py']
22+
addopts = --ignore=setup.py

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
requirements = [ 'boto3' ]
1414

15-
test_requirements = ['pytest>=3', ]
15+
test_requirements = ['pytest>=3', 'pytest-mock>=3', ]
1616

1717
setup(
1818
author="Janos Tolgyesi",

tests/test_dynamodb_mapping.py

Lines changed: 143 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,153 @@
44

55
import pytest
66

7+
from dynamodb_mapping import DynamoDBMapping
78

8-
# from dynamodb_mapping import DynamoDBMapping
9+
TEST_TABLE_HASH_KEY_NAME = "test_primary_key"
910

10-
# mapping = DynamoDBMapping("foobar")
11+
TEST_ITEM1_KEY = "first_item"
12+
TEST_ITEM1 = {
13+
TEST_TABLE_HASH_KEY_NAME: TEST_ITEM1_KEY,
14+
"foo": "bar"
15+
}
16+
17+
TEST_ITEM2_KEY = "second_item"
18+
TEST_ITEM2 = {
19+
TEST_TABLE_HASH_KEY_NAME: TEST_ITEM2_KEY,
20+
"foo2": "bar2"
21+
}
22+
23+
TEST_ATTRIBUTES = {"foo": "bar"}
1124

1225
@pytest.fixture
13-
def response():
14-
"""Sample pytest fixture.
26+
def mapping(mocker):
27+
boto3_session = mocker.MagicMock()
28+
boto3_session.resource().Table().key_schema = [
29+
{"AttributeName": TEST_TABLE_HASH_KEY_NAME, "KeyType": "HASH"}
30+
]
31+
return DynamoDBMapping("table_name", boto3_session=boto3_session)
32+
33+
34+
def test_init(mapping):
35+
assert mapping.key_names == (TEST_TABLE_HASH_KEY_NAME,)
36+
37+
def test_scan(mapping, mocker):
38+
mapping.table.scan = mocker.MagicMock(return_value={"Items": [TEST_ITEM1]})
39+
assert next(mapping.scan()) == TEST_ITEM1
40+
41+
def test_scan_pages(mapping, mocker):
42+
mapping.table.scan = mocker.MagicMock(side_effect=[
43+
{"Items": [TEST_ITEM1], "LastEvaluatedKey": "to_be_continued"}, {"Items": [TEST_ITEM2]}
44+
])
45+
results = mapping.scan()
46+
assert next(results) == TEST_ITEM1
47+
assert next(results) == TEST_ITEM2
48+
assert mapping.table.scan.call_count == 2
49+
50+
def test_get_item(mapping, mocker):
51+
mapping.table.get_item = mocker.MagicMock(return_value={"Item": TEST_ITEM1})
52+
assert mapping.get_item(TEST_ITEM1_KEY) == TEST_ITEM1
53+
mapping.table.get_item.assert_called_with(Key={TEST_TABLE_HASH_KEY_NAME: TEST_ITEM1_KEY})
54+
55+
def test_invalid_keys(mapping):
56+
with pytest.raises(ValueError):
57+
mapping.get_item((TEST_ITEM1_KEY, TEST_ITEM2_KEY))
58+
59+
def test_get_item_non_existing(mapping, mocker):
60+
mapping.table.get_item = mocker.MagicMock(return_value={})
61+
with pytest.raises(KeyError):
62+
mapping.get_item(TEST_ITEM1_KEY)
63+
64+
def test_get_item_accessor(mapping, mocker):
65+
mapping.table.get_item = mocker.MagicMock(return_value={"Item": TEST_ITEM1})
66+
mapping.modify_item = mocker.MagicMock()
67+
accessor = mapping.get_item(TEST_ITEM1_KEY)
68+
mapping.table.get_item.assert_called_with(Key={TEST_TABLE_HASH_KEY_NAME: TEST_ITEM1_KEY})
69+
accessor["new_attrib"] = "foobar"
70+
mapping.modify_item.assert_called_with(TEST_ITEM1_KEY, {"new_attrib": "foobar"})
71+
72+
def test_set_item(mapping, mocker):
73+
mapping.table.put_item = mocker.MagicMock()
74+
mapping.set_item(TEST_ITEM1_KEY, TEST_ATTRIBUTES)
75+
mapping.table.put_item.assert_called_with(
76+
Item={TEST_TABLE_HASH_KEY_NAME: TEST_ITEM1_KEY, **TEST_ATTRIBUTES}
77+
)
78+
79+
def test_put_item(mapping, mocker):
80+
mapping.set_item = mocker.MagicMock()
81+
mapping.put_item(TEST_ITEM1_KEY, TEST_ATTRIBUTES)
82+
mapping.set_item.assert_called_with(TEST_ITEM1_KEY, TEST_ATTRIBUTES)
83+
84+
def test_del_item(mapping, mocker):
85+
mapping.table.delete_item = mocker.MagicMock()
86+
mapping.del_item(TEST_ITEM1_KEY, check_existing=False)
87+
mapping.table.delete_item.assert_called_with(Key={TEST_TABLE_HASH_KEY_NAME: TEST_ITEM1_KEY})
88+
89+
def test_del_item_non_existing(mapping, mocker):
90+
mapping.table.delete_item = mocker.MagicMock()
91+
mapping.keys = mocker.MagicMock(return_value=[TEST_ITEM2_KEY])
92+
with pytest.raises(KeyError):
93+
mapping.del_item(TEST_ITEM1_KEY)
94+
95+
def test_modify_item(mapping, mocker):
96+
mapping.table.update_item = mocker.MagicMock()
97+
mapping.modify_item(TEST_ITEM1_KEY, {"new1": "foobar!", "new2": "bar_foo!", "zombie": None})
98+
mapping.table.update_item.assert_called_with(
99+
Key={TEST_TABLE_HASH_KEY_NAME: TEST_ITEM1_KEY},
100+
UpdateExpression='set #key0 = :value0, #key1 = :value1 remove #key2',
101+
ExpressionAttributeValues={':value0': 'foobar!', ':value1': 'bar_foo!'},
102+
ExpressionAttributeNames={'#key0': 'new1', '#key1': 'new2', '#key2': 'zombie'},
103+
)
104+
105+
def test_modify_empty(mapping):
106+
with pytest.warns(UserWarning):
107+
mapping.modify_item(TEST_ITEM1_KEY, {})
108+
109+
def test_op_iter(mapping, mocker):
110+
mapping.scan = mocker.MagicMock(return_value=[
111+
{TEST_TABLE_HASH_KEY_NAME: TEST_ITEM1_KEY},
112+
{TEST_TABLE_HASH_KEY_NAME: TEST_ITEM2_KEY},
113+
])
114+
assert list(mapping) == [TEST_ITEM1_KEY, TEST_ITEM2_KEY]
115+
mapping.scan.assert_called_with(ProjectionExpression=TEST_TABLE_HASH_KEY_NAME)
116+
117+
def test_op_len(mapping):
118+
mapping.table.item_count = 42
119+
assert len(mapping) == 42
120+
121+
def test_op_getitem(mapping, mocker):
122+
mapping.get_item = mocker.MagicMock(return_value=TEST_ITEM1)
123+
assert mapping[TEST_ITEM1_KEY] == TEST_ITEM1
124+
mapping.get_item.assert_called_with(TEST_ITEM1_KEY)
125+
126+
def test_op_setitem(mapping, mocker):
127+
mapping.set_item = mocker.MagicMock()
128+
mapping[TEST_ITEM1_KEY] = TEST_ITEM1
129+
mapping.set_item.assert_called_with(TEST_ITEM1_KEY, TEST_ITEM1)
130+
131+
def test_op_delitem(mapping, mocker):
132+
mapping.del_item = mocker.MagicMock()
133+
del mapping[TEST_ITEM1_KEY]
134+
mapping.del_item.assert_called_with(TEST_ITEM1_KEY)
135+
136+
def test_items_view(mapping, mocker):
137+
mapping.scan = mocker.MagicMock(return_value=[TEST_ITEM1, TEST_ITEM2])
138+
items = mapping.items()
139+
assert list(items) == [(TEST_ITEM1_KEY, TEST_ITEM1), (TEST_ITEM2_KEY, TEST_ITEM2)]
15140

16-
See more at: http://doc.pytest.org/en/latest/fixture.html
17-
"""
18-
# import requests
19-
# return requests.get('https://github.com/audreyr/cookiecutter-pypackage')
141+
def test_values_view(mapping, mocker):
142+
mapping.scan = mocker.MagicMock(return_value=[TEST_ITEM1])
143+
values = mapping.values()
144+
assert TEST_ITEM1 in values
145+
assert TEST_ITEM2 not in values
146+
assert list(values) == [TEST_ITEM1]
20147

148+
def test_keys_view(mapping, mocker):
149+
mapping.table.get_item = mocker.MagicMock(side_effect=[{"Item": TEST_ITEM1}])
150+
keys = mapping.keys()
151+
assert TEST_ITEM1_KEY in keys
21152

22-
def test_content(response):
23-
"""Sample pytest test function with the pytest fixture as an argument."""
24-
# from bs4 import BeautifulSoup
25-
# assert 'GitHub' in BeautifulSoup(response.content).title.string
153+
def test_keys_view_non_existing(mapping, mocker):
154+
mapping.table.get_item = mocker.MagicMock(side_effect=[{}])
155+
keys = mapping.keys()
156+
assert TEST_ITEM1_KEY not in keys

0 commit comments

Comments
 (0)