Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 0643c68

Browse files
committed
Add basic test for each generated client method
1 parent e176020 commit 0643c68

1 file changed

Lines changed: 85 additions & 0 deletions

File tree

gapic/templates/tests/unit/%name_%version/%sub/test_%service.py.j2

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import mock
55

66
import grpc
7+
from grpc.experimental import aio
78
import math
89
import pytest
910

@@ -17,6 +18,7 @@ from {{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + ser
1718
from {{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join(".") }}.services.{{ service.name|snake_case }} import transports
1819
from google.api_core import client_options
1920
from google.api_core import grpc_helpers
21+
from google.api_core import grpc_helpers_async
2022
{% if service.has_lro -%}
2123
from google.api_core import future
2224
from google.api_core import operations_v1
@@ -213,6 +215,89 @@ def test_{{ method.name|snake_case }}(transport: str = 'grpc'):
213215
{% endfor %}
214216
{% endif %}
215217

218+
219+
@pytest.mark.asyncio
220+
async def test_{{ method.name|snake_case }}_async(transport: str = 'grpc_asyncio'):
221+
client = {{ service.async_client_name }}(
222+
credentials=credentials.AnonymousCredentials(),
223+
transport=transport,
224+
)
225+
226+
# Everything is optional in proto3 as far as the runtime is concerned,
227+
# and we are mocking out the actual API, so just send an empty request.
228+
request = {{ method.input.ident }}()
229+
{% if method.client_streaming %}
230+
requests = [request]
231+
{% endif %}
232+
233+
# Mock the actual call within the gRPC stub, and fake the request.
234+
with mock.patch.object(
235+
type(client._client._transport.{{ method.name|snake_case }}),
236+
'__call__') as call:
237+
# Designate an appropriate return value for the call.
238+
{% if method.void -%}
239+
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None)
240+
{% elif method.lro -%}
241+
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
242+
operations_pb2.Operation(name='operations/spam')
243+
)
244+
{% elif not method.client_streaming and method.server_streaming -%}
245+
call.return_value = mock.Mock(aio.UnaryStreamCall, autospec=True)
246+
call.return_value.read = mock.AsyncMock(side_effect=[{{ method.output.ident }}()])
247+
{% elif method.client_streaming and method.server_streaming -%}
248+
call.return_value = mock.Mock(aio.StreamStreamCall, autospec=True)
249+
call.return_value.read = mock.AsyncMock(side_effect=[{{ method.output.ident }}()])
250+
{% else -%}
251+
call.return_value ={{' '}}
252+
{%- if not method.client_streaming and not method.server_streaming -%}
253+
grpc_helpers_async.FakeUnaryUnaryCall
254+
{%- else -%}
255+
grpc_helpers_async.FakeStreamUnaryCall
256+
{%- endif -%}({{ method.output.ident }}(
257+
{%- for field in method.output.fields.values() | rejectattr('message') %}
258+
{{ field.name }}={{ field.mock_value }},
259+
{%- endfor %}
260+
))
261+
{% endif -%}
262+
{% if method.client_streaming and method.server_streaming %}
263+
response = await client.{{ method.name|snake_case }}(iter(requests))
264+
{% elif method.client_streaming and not method.server_streaming %}
265+
response = await (await client.{{ method.name|snake_case }}(iter(requests)))
266+
{% else %}
267+
response = await client.{{ method.name|snake_case }}(request)
268+
{% endif %}
269+
270+
# Establish that the underlying gRPC stub method was called.
271+
assert len(call.mock_calls)
272+
_, args, _ = call.mock_calls[0]
273+
{% if method.client_streaming %}
274+
assert next(args[0]) == request
275+
{% else %}
276+
assert args[0] == request
277+
{% endif %}
278+
279+
# Establish that the response is the type that we expect.
280+
{% if method.void -%}
281+
assert response is None
282+
{% elif method.lro -%}
283+
assert isinstance(response, future.Future)
284+
{% elif method.server_streaming -%}
285+
message = await response.read()
286+
assert isinstance(message, {{ method.output.ident }})
287+
{% else -%}
288+
assert isinstance(response, {{ method.client_output_async.ident }})
289+
{% for field in method.output.fields.values() | rejectattr('message') -%}
290+
{% if field.field_pb.type in [1, 2] -%} {# Use approx eq for floats -#}
291+
assert math.isclose(response.{{ field.name }}, {{ field.mock_value }}, rel_tol=1e-6)
292+
{% elif field.field_pb.type == 8 -%} {# Use 'is' for bools #}
293+
assert response.{{ field.name }} is {{ field.mock_value }}
294+
{% else -%}
295+
assert response.{{ field.name }} == {{ field.mock_value }}
296+
{% endif -%}
297+
{% endfor %}
298+
{% endif %}
299+
300+
216301
{% if method.field_headers %}
217302
def test_{{ method.name|snake_case }}_field_headers():
218303
client = {{ service.client_name }}(

0 commit comments

Comments
 (0)