Skip to content

Commit 3bcc674

Browse files
committed
Use MagicMocks with async_return_value and pytest.mark.asyncio
1 parent b14595b commit 3bcc674

17 files changed

Lines changed: 495 additions & 465 deletions

src/galaxy/unittest/mock.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@
33

44

55
class AsyncMock(MagicMock):
6+
"""
7+
..deprecated:: 0.45
8+
Use: :class:`MagicMock` with meth:`~.async_return_value`.
9+
"""
610
async def __call__(self, *args, **kwargs):
711
return super(AsyncMock, self).__call__(*args, **kwargs)
812

913

1014
def coroutine_mock():
15+
"""
16+
..deprecated:: 0.45
17+
Use: :class:`MagicMock` with meth:`~.async_return_value`.
18+
"""
1119
coro = MagicMock(name="CoroutineResult")
1220
corofunc = MagicMock(name="CoroutineFunction", side_effect=asyncio.coroutine(coro))
1321
corofunc.coro = coro
1422
return corofunc
1523

16-
1724
async def skip_loop(iterations=1):
1825
for _ in range(iterations):
1926
await asyncio.sleep(0)

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ def get_messages(write_mock):
1616
message = json.loads(line)
1717
messages.append(message)
1818
return messages
19+

tests/conftest.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,19 @@
66

77
from galaxy.api.plugin import Plugin
88
from galaxy.api.consts import Platform
9-
from galaxy.unittest.mock import AsyncMock, coroutine_mock, skip_loop
109

1110
@pytest.fixture()
1211
def reader():
1312
stream = MagicMock(name="stream_reader")
14-
stream.read = AsyncMock()
13+
stream.read = MagicMock()
1514
yield stream
1615

1716
@pytest.fixture()
1817
async def writer():
1918
stream = MagicMock(name="stream_writer")
2019
stream.write = MagicMock()
21-
stream.drain = AsyncMock(return_value=None)
20+
stream.drain = MagicMock()
2221
yield stream
23-
await skip_loop(1) # drain
2422

2523
@pytest.fixture()
2624
def read(reader):
@@ -33,7 +31,7 @@ def write(writer):
3331
@pytest.fixture()
3432
def plugin(reader, writer):
3533
"""Return plugin instance with all feature methods mocked"""
36-
async_methods = (
34+
methods = (
3735
"handshake_complete",
3836
"authenticate",
3937
"get_owned_games",
@@ -46,17 +44,12 @@ def plugin(reader, writer):
4644
"get_friends",
4745
"get_game_time",
4846
"prepare_game_times_context",
49-
"shutdown_platform_client"
50-
)
51-
52-
methods = (
47+
"shutdown_platform_client",
5348
"shutdown",
5449
"tick"
5550
)
5651

5752
with ExitStack() as stack:
58-
for method in async_methods:
59-
stack.enter_context(patch.object(Plugin, method, new_callable=coroutine_mock))
6053
for method in methods:
6154
stack.enter_context(patch.object(Plugin, method))
6255
yield Plugin(Platform.Generic, "0.1", reader, writer, "token")

tests/test_achievements.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import json
2-
from unittest.mock import MagicMock
32

43
import pytest
54
from pytest import raises
@@ -21,17 +20,9 @@ def test_initialization_no_id_nor_name():
2120
Achievement(unlock_time=1234567890)
2221

2322

24-
# TODO replace AsyncMocks with MagicMocks in conftest and use async_return_value
25-
@pytest.fixture()
26-
def reader():
27-
stream = MagicMock(name="stream_reader")
28-
stream.read = MagicMock()
29-
yield stream
30-
31-
3223
@pytest.mark.asyncio
3324
async def test_get_unlocked_achievements_success(plugin, read, write):
34-
plugin.prepare_achievements_context.coro.return_value = 5
25+
plugin.prepare_achievements_context.return_value = async_return_value(5)
3526
request = {
3627
"jsonrpc": "2.0",
3728
"id": "3",
@@ -41,11 +32,11 @@ async def test_get_unlocked_achievements_success(plugin, read, write):
4132
}
4233
}
4334
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"", 10)]
44-
plugin.get_unlocked_achievements.coro.return_value = [
35+
plugin.get_unlocked_achievements.return_value = async_return_value([
4536
Achievement(achievement_id="lvl10", unlock_time=1548421241),
4637
Achievement(achievement_name="Got level 20", unlock_time=1548422395),
4738
Achievement(achievement_id="lvl30", achievement_name="Got level 30", unlock_time=1548495633)
48-
]
39+
])
4940
await plugin.run()
5041
plugin.prepare_achievements_context.assert_called_with(["14"])
5142
plugin.get_unlocked_achievements.assert_called_with("14", 5)
@@ -92,6 +83,7 @@ async def test_get_unlocked_achievements_success(plugin, read, write):
9283
(KeyError, 0, "Unknown error")
9384
])
9485
async def test_get_unlocked_achievements_error(exception, code, message, plugin, read, write):
86+
plugin.prepare_achievements_context.return_value = async_return_value(None)
9587
request = {
9688
"jsonrpc": "2.0",
9789
"id": "3",
@@ -102,7 +94,7 @@ async def test_get_unlocked_achievements_error(exception, code, message, plugin,
10294
}
10395

10496
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"", 10)]
105-
plugin.get_unlocked_achievements.coro.side_effect = exception
97+
plugin.get_unlocked_achievements.side_effect = exception
10698
await plugin.run()
10799
plugin.get_unlocked_achievements.assert_called()
108100

@@ -132,7 +124,7 @@ async def test_get_unlocked_achievements_error(exception, code, message, plugin,
132124

133125
@pytest.mark.asyncio
134126
async def test_prepare_get_unlocked_achievements_context_error(plugin, read, write):
135-
plugin.prepare_achievements_context.coro.side_effect = BackendError()
127+
plugin.prepare_achievements_context.side_effect = BackendError()
136128
request = {
137129
"jsonrpc": "2.0",
138130
"id": "3",
@@ -158,6 +150,7 @@ async def test_prepare_get_unlocked_achievements_context_error(plugin, read, wri
158150

159151
@pytest.mark.asyncio
160152
async def test_import_in_progress(plugin, read, write):
153+
plugin.prepare_achievements_context.return_value = async_return_value(None)
161154
requests = [
162155
{
163156
"jsonrpc": "2.0",

tests/test_authenticate.py

Lines changed: 60 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,40 @@
1-
import asyncio
2-
import json
3-
41
import pytest
52

63
from galaxy.api.types import Authentication
74
from galaxy.api.errors import (
85
UnknownError, InvalidCredentials, NetworkError, LoggedInElsewhere, ProtocolError,
96
BackendNotAvailable, BackendTimeout, BackendError, TemporaryBlocked, Banned, AccessDenied
107
)
8+
from galaxy.unittest.mock import async_return_value
9+
10+
from tests import create_message, get_messages
11+
1112

12-
def test_success(plugin, read, write):
13+
@pytest.mark.asyncio
14+
async def test_success(plugin, read, write):
1315
request = {
1416
"jsonrpc": "2.0",
1517
"id": "3",
1618
"method": "init_authentication"
1719
}
18-
19-
read.side_effect = [json.dumps(request).encode() + b"\n", b""]
20-
plugin.authenticate.coro.return_value = Authentication("132", "Zenek")
21-
asyncio.run(plugin.run())
20+
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"")]
21+
plugin.authenticate.return_value = async_return_value(Authentication("132", "Zenek"))
22+
await plugin.run()
2223
plugin.authenticate.assert_called_with()
23-
response = json.loads(write.call_args[0][0])
2424

25-
assert response == {
26-
"jsonrpc": "2.0",
27-
"id": "3",
28-
"result": {
29-
"user_id": "132",
30-
"user_name": "Zenek"
25+
assert get_messages(write) == [
26+
{
27+
"jsonrpc": "2.0",
28+
"id": "3",
29+
"result": {
30+
"user_id": "132",
31+
"user_name": "Zenek"
32+
}
3133
}
32-
}
34+
]
35+
3336

37+
@pytest.mark.asyncio
3438
@pytest.mark.parametrize("error,code,message", [
3539
pytest.param(UnknownError, 0, "Unknown error", id="unknown_error"),
3640
pytest.param(BackendNotAvailable, 2, "Backend not available", id="backend_not_available"),
@@ -44,29 +48,32 @@ def test_success(plugin, read, write):
4448
pytest.param(Banned, 105, "Banned", id="banned"),
4549
pytest.param(AccessDenied, 106, "Access denied", id="access_denied"),
4650
])
47-
def test_failure(plugin, read, write, error, code, message):
51+
async def test_failure(plugin, read, write, error, code, message):
4852
request = {
4953
"jsonrpc": "2.0",
5054
"id": "3",
5155
"method": "init_authentication"
5256
}
5357

54-
read.side_effect = [json.dumps(request).encode() + b"\n", b""]
55-
plugin.authenticate.coro.side_effect = error()
56-
asyncio.run(plugin.run())
58+
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"")]
59+
plugin.authenticate.side_effect = error()
60+
await plugin.run()
5761
plugin.authenticate.assert_called_with()
58-
response = json.loads(write.call_args[0][0])
5962

60-
assert response == {
61-
"jsonrpc": "2.0",
62-
"id": "3",
63-
"error": {
64-
"code": code,
65-
"message": message
63+
assert get_messages(write) == [
64+
{
65+
"jsonrpc": "2.0",
66+
"id": "3",
67+
"error": {
68+
"code": code,
69+
"message": message
70+
}
6671
}
67-
}
72+
]
73+
6874

69-
def test_stored_credentials(plugin, read, write):
75+
@pytest.mark.asyncio
76+
async def test_stored_credentials(plugin, read, write):
7077
request = {
7178
"jsonrpc": "2.0",
7279
"id": "3",
@@ -77,39 +84,37 @@ def test_stored_credentials(plugin, read, write):
7784
}
7885
}
7986
}
80-
read.side_effect = [json.dumps(request).encode() + b"\n", b""]
81-
plugin.authenticate.coro.return_value = Authentication("132", "Zenek")
82-
asyncio.run(plugin.run())
87+
read.side_effect = [async_return_value(create_message(request)), async_return_value(b"")]
88+
plugin.authenticate.return_value = async_return_value(Authentication("132", "Zenek"))
89+
await plugin.run()
8390
plugin.authenticate.assert_called_with(stored_credentials={"token": "ABC"})
8491
write.assert_called()
8592

86-
def test_store_credentials(plugin, write):
93+
94+
@pytest.mark.asyncio
95+
async def test_store_credentials(plugin, write):
8796
credentials = {
8897
"token": "ABC"
8998
}
99+
plugin.store_credentials(credentials)
90100

91-
async def couritine():
92-
plugin.store_credentials(credentials)
93-
94-
asyncio.run(couritine())
95-
response = json.loads(write.call_args[0][0])
96-
97-
assert response == {
98-
"jsonrpc": "2.0",
99-
"method": "store_credentials",
100-
"params": credentials
101-
}
102-
103-
def test_lost_authentication(plugin, write):
101+
assert get_messages(write) == [
102+
{
103+
"jsonrpc": "2.0",
104+
"method": "store_credentials",
105+
"params": credentials
106+
}
107+
]
104108

105-
async def couritine():
106-
plugin.lost_authentication()
107109

108-
asyncio.run(couritine())
109-
response = json.loads(write.call_args[0][0])
110+
@pytest.mark.asyncio
111+
async def test_lost_authentication(plugin, write):
112+
plugin.lost_authentication()
110113

111-
assert response == {
112-
"jsonrpc": "2.0",
113-
"method": "authentication_lost",
114-
"params": None
115-
}
114+
assert get_messages(write) == [
115+
{
116+
"jsonrpc": "2.0",
117+
"method": "authentication_lost",
118+
"params": None
119+
}
120+
]

tests/test_chunk_messages.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import asyncio
21
import json
32

4-
def test_chunked_messages(plugin, read):
3+
import pytest
4+
5+
from galaxy.unittest.mock import async_return_value
6+
7+
8+
@pytest.mark.asyncio
9+
async def test_chunked_messages(plugin, read):
510
request = {
611
"jsonrpc": "2.0",
712
"method": "install_game",
@@ -11,11 +16,13 @@ def test_chunked_messages(plugin, read):
1116
}
1217

1318
message = json.dumps(request).encode() + b"\n"
14-
read.side_effect = [message[:5], message[5:], b""]
15-
asyncio.run(plugin.run())
19+
read.side_effect = [async_return_value(message[:5]), async_return_value(message[5:]), async_return_value(b"")]
20+
await plugin.run()
1621
plugin.install_game.assert_called_with(game_id="3")
1722

18-
def test_joined_messages(plugin, read):
23+
24+
@pytest.mark.asyncio
25+
async def test_joined_messages(plugin, read):
1926
requests = [
2027
{
2128
"jsonrpc": "2.0",
@@ -34,12 +41,14 @@ def test_joined_messages(plugin, read):
3441
]
3542
data = b"".join([json.dumps(request).encode() + b"\n" for request in requests])
3643

37-
read.side_effect = [data, b""]
38-
asyncio.run(plugin.run())
44+
read.side_effect = [async_return_value(data), async_return_value(b"")]
45+
await plugin.run()
3946
plugin.install_game.assert_called_with(game_id="3")
4047
plugin.launch_game.assert_called_with(game_id="3")
4148

42-
def test_not_finished(plugin, read):
49+
50+
@pytest.mark.asyncio
51+
async def test_not_finished(plugin, read):
4352
request = {
4453
"jsonrpc": "2.0",
4554
"method": "install_game",
@@ -49,6 +58,6 @@ def test_not_finished(plugin, read):
4958
}
5059

5160
message = json.dumps(request).encode() # no new line
52-
read.side_effect = [message, b""]
53-
asyncio.run(plugin.run())
61+
read.side_effect = [async_return_value(message), async_return_value(b"")]
62+
await plugin.run()
5463
plugin.install_game.assert_not_called()

0 commit comments

Comments
 (0)