Skip to content

Commit 21c0c79

Browse files
committed
test: fix unit test coverage for google-cloud-bigquery
1 parent e1f00d4 commit 21c0c79

7 files changed

Lines changed: 264 additions & 47 deletions

File tree

packages/google-cloud-bigquery/.coveragerc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ exclude_lines =
1212
pragma: (no cover|NO COVER)
1313
# Ignore debug-only repr
1414
def __repr__
15+
except ImportError:
16+
except ImportError as .*:

packages/google-cloud-bigquery/google/cloud/bigquery/magics/magics.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323

2424
from __future__ import print_function
2525

26-
import re
2726
import ast
2827
import copy
2928
import functools
29+
import re
3030
import sys
3131
import time
3232
import warnings
@@ -39,14 +39,12 @@
3939
except ImportError:
4040
raise ImportError("This module can only be loaded in IPython.")
4141

42-
from google.api_core import client_info
43-
from google.api_core import client_options
44-
from google.api_core.exceptions import NotFound
4542
import google.auth # type: ignore
46-
from google.cloud import bigquery
4743
import google.cloud.bigquery.dataset
48-
from google.cloud.bigquery import _versions_helpers
49-
from google.cloud.bigquery import exceptions
44+
from google.api_core import client_info, client_options
45+
from google.api_core.exceptions import NotFound
46+
from google.cloud import bigquery
47+
from google.cloud.bigquery import _versions_helpers, exceptions
5048
from google.cloud.bigquery.dbapi import _helpers
5149
from google.cloud.bigquery.magics import line_arg_parser as lap
5250

@@ -231,7 +229,7 @@ def progress_bar_type(self, value):
231229
# their code.
232230
if bigquery_magics is not None:
233231
context = bigquery_magics.context
234-
else:
232+
else: # pragma: NO COVER
235233
context = Context()
236234

237235

packages/google-cloud-bigquery/tests/unit/test__pandas_helpers.py

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@
1818
import decimal
1919
import functools
2020
import gc
21+
import importlib.metadata as metadata
2122
import operator
2223
import queue
2324
import time
25+
import warnings
2426
from typing import Union
2527
from unittest import mock
26-
import warnings
27-
28-
import importlib.metadata as metadata
2928

3029
try:
3130
import pandas
@@ -45,13 +44,13 @@
4544
geopandas = None
4645

4746
import pytest
48-
4947
from google import api_core
50-
51-
from google.cloud.bigquery import exceptions
52-
from google.cloud.bigquery import _pyarrow_helpers
53-
from google.cloud.bigquery import _versions_helpers
54-
from google.cloud.bigquery import schema
48+
from google.cloud.bigquery import (
49+
_pyarrow_helpers,
50+
_versions_helpers,
51+
exceptions,
52+
schema,
53+
)
5554
from google.cloud.bigquery._pandas_helpers import determine_requested_streams
5655

5756
pyarrow = _versions_helpers.PYARROW_VERSIONS.try_import()
@@ -1831,8 +1830,7 @@ def test__download_table_bqstorage(
18311830
expected_call_count,
18321831
expected_maxsize,
18331832
):
1834-
from google.cloud.bigquery import dataset
1835-
from google.cloud.bigquery import table
1833+
from google.cloud.bigquery import dataset, table
18361834

18371835
queue_used = None # A reference to the queue used by code under test.
18381836

@@ -1885,10 +1883,9 @@ def test__download_table_bqstorage_shuts_down_workers(
18851883
the child threads are also stopped.
18861884
"""
18871885
pytest.importorskip("google.cloud.bigquery_storage_v1")
1888-
from google.cloud.bigquery import dataset
1889-
from google.cloud.bigquery import table
18901886
import google.cloud.bigquery_storage_v1.reader
18911887
import google.cloud.bigquery_storage_v1.types
1888+
from google.cloud.bigquery import dataset, table
18921889

18931890
monkeypatch.setattr(
18941891
_versions_helpers.BQ_STORAGE_VERSIONS, "_installed_version", None
@@ -2211,10 +2208,10 @@ def test_determine_requested_streams_invalid_max_stream_count():
22112208
bigquery_storage is None, reason="Requires google-cloud-bigquery-storage"
22122209
)
22132210
def test__download_table_bqstorage_w_timeout_error(module_under_test):
2214-
from google.cloud.bigquery import dataset
2215-
from google.cloud.bigquery import table
22162211
from unittest import mock
22172212

2213+
from google.cloud.bigquery import dataset, table
2214+
22182215
mock_bqstorage_client = mock.create_autospec(
22192216
bigquery_storage.BigQueryReadClient, instance=True
22202217
)
@@ -2248,10 +2245,10 @@ def slow_download_stream(
22482245
bigquery_storage is None, reason="Requires google-cloud-bigquery-storage"
22492246
)
22502247
def test__download_table_bqstorage_w_timeout_success(module_under_test):
2251-
from google.cloud.bigquery import dataset
2252-
from google.cloud.bigquery import table
22532248
from unittest import mock
22542249

2250+
from google.cloud.bigquery import dataset, table
2251+
22552252
mock_bqstorage_client = mock.create_autospec(
22562253
bigquery_storage.BigQueryReadClient, instance=True
22572254
)
@@ -2409,3 +2406,38 @@ def test_download_arrow_bqstorage_passes_timeout_to_create_read_session(
24092406
assert retry_policy is not None
24102407
# Check if deadline is set correctly in the retry policy
24112408
assert retry_policy._deadline == timeout
2409+
2410+
2411+
@pytest.mark.skipif(pandas is None, reason="Requires `pandas`")
2412+
def test_dataframe_to_bq_schema_w_unused_schema_field(module_under_test):
2413+
with mock.patch.object(module_under_test, "pandas_gbq", None):
2414+
with pytest.raises(
2415+
ValueError, match="bq_schema contains fields not present in dataframe"
2416+
):
2417+
module_under_test.dataframe_to_bq_schema(
2418+
pandas.DataFrame(), (schema.SchemaField("not_in_df", "STRING"),)
2419+
)
2420+
2421+
2422+
@pytest.mark.skipif(pandas is None, reason="Requires `pandas`")
2423+
@pytest.mark.skipif(isinstance(pyarrow, mock.Mock), reason="Requires `pyarrow`")
2424+
def test_get_schema_by_pyarrow_bignumeric(module_under_test):
2425+
series = pandas.Series([decimal.Decimal("1.12345678901")])
2426+
result = module_under_test._get_schema_by_pyarrow("col", series)
2427+
assert result is not None
2428+
assert result.field_type == "BIGNUMERIC"
2429+
2430+
2431+
@pytest.mark.skipif(pandas is None, reason="Requires `pandas`")
2432+
@pytest.mark.skipif(isinstance(pyarrow, mock.Mock), reason="Requires `pyarrow`")
2433+
def test_get_types_mapper_range_timestamp_mismatch(module_under_test):
2434+
if not hasattr(pandas, "ArrowDtype"):
2435+
return
2436+
range_ts = pandas.ArrowDtype(
2437+
pyarrow.struct(
2438+
[("start", pyarrow.timestamp("us")), ("end", pyarrow.timestamp("us"))]
2439+
)
2440+
)
2441+
mapper = module_under_test.default_types_mapper(range_timestamp_dtype=range_ts)
2442+
unmatched_struct = pyarrow.struct([("other", pyarrow.int64())])
2443+
assert mapper(unmatched_struct) is None

packages/google-cloud-bigquery/tests/unit/test__pyarrow_helpers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,16 @@ def test_bq_to_arrow_scalars(module_under_test):
4444
def test_arrow_scalar_ids_to_bq(module_under_test):
4545
assert module_under_test.arrow_scalar_ids_to_bq(pyarrow.bool_().id) == "BOOL"
4646
assert module_under_test.arrow_scalar_ids_to_bq("UNKNOWN_TYPE") is None
47+
48+
49+
def test_pyarrow_helpers_when_pyarrow_none(module_under_test):
50+
import importlib
51+
import sys
52+
from unittest import mock
53+
54+
with mock.patch.dict(sys.modules, {"pyarrow": None}):
55+
importlib.reload(module_under_test)
56+
assert module_under_test.pyarrow is None
57+
assert module_under_test.arrow_scalar_ids_to_bq(1) is None
58+
59+
importlib.reload(module_under_test)

packages/google-cloud-bigquery/tests/unit/test__versions_helpers.py

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ def test_try_import_raises_error_w_legacy_pyarrow():
5858
versions.try_import(raise_if_error=True)
5959

6060

61-
@pytest.mark.skipif(
62-
pyarrow is not None,
63-
reason="pyarrow is installed, but this test needs it not to be",
64-
)
6561
def test_try_import_raises_error_w_no_pyarrow():
62+
import sys
63+
6664
versions = _versions_helpers.PyarrowVersions()
67-
with pytest.raises(exceptions.LegacyPyarrowError):
68-
versions.try_import(raise_if_error=True)
65+
with mock.patch.dict(sys.modules, {"pyarrow": None}):
66+
assert versions.try_import(raise_if_error=False) is None
67+
with pytest.raises(exceptions.LegacyPyarrowError):
68+
versions.try_import(raise_if_error=True)
6969

7070

7171
@pytest.mark.skipif(pyarrow is None, reason="pyarrow is not installed")
@@ -121,17 +121,29 @@ def test_returns_none_with_legacy_bqstorage():
121121
assert bq_storage is None
122122

123123

124-
@pytest.mark.skipif(
125-
bigquery_storage is not None,
126-
reason="Tests behavior when `google-cloud-bigquery-storage` isn't installed",
127-
)
128124
def test_returns_none_with_bqstorage_uninstalled():
129-
try:
130-
bqstorage_versions = _versions_helpers.BQStorageVersions()
131-
bq_storage = bqstorage_versions.try_import()
132-
except exceptions.LegacyBigQueryStorageError: # pragma: NO COVER
133-
raise ("NotFound error raised when raise_if_error == False.")
134-
assert bq_storage is None
125+
import sys
126+
127+
from google import cloud
128+
129+
versions = _versions_helpers.BQStorageVersions()
130+
with mock.patch.dict(sys.modules, {"google.cloud.bigquery_storage": None}):
131+
with mock.patch.dict(cloud.__dict__):
132+
cloud.__dict__.pop("bigquery_storage", None)
133+
assert versions.try_import() is None
134+
135+
136+
def test_raises_error_with_bqstorage_uninstalled():
137+
import sys
138+
139+
from google import cloud
140+
141+
versions = _versions_helpers.BQStorageVersions()
142+
with mock.patch.dict(sys.modules, {"google.cloud.bigquery_storage": None}):
143+
with mock.patch.dict(cloud.__dict__):
144+
cloud.__dict__.pop("bigquery_storage", None)
145+
with pytest.raises(exceptions.BigQueryStorageNotFoundError):
146+
versions.try_import(raise_if_error=True)
135147

136148

137149
@pytest.mark.skipif(
@@ -219,14 +231,14 @@ def test_try_import_raises_error_w_legacy_pandas():
219231
versions.try_import(raise_if_error=True)
220232

221233

222-
@pytest.mark.skipif(
223-
pandas is not None,
224-
reason="pandas is installed, but this test needs it not to be",
225-
)
226234
def test_try_import_raises_error_w_no_pandas():
235+
import sys
236+
227237
versions = _versions_helpers.PandasVersions()
228-
with pytest.raises(exceptions.LegacyPandasError):
229-
versions.try_import(raise_if_error=True)
238+
with mock.patch.dict(sys.modules, {"pandas": None}):
239+
assert versions.try_import(raise_if_error=False) is None
240+
with pytest.raises(exceptions.LegacyPandasError):
241+
versions.try_import(raise_if_error=True)
230242

231243

232244
@pytest.mark.skipif(pandas is None, reason="pandas is not installed")
@@ -255,6 +267,7 @@ def test_installed_pandas_gbq_version_returns_cached():
255267

256268
def test_installed_pandas_gbq_version_returns_parsed_version():
257269
import sys
270+
258271
mock_pandas_gbq = mock.Mock()
259272
mock_pandas_gbq.__version__ = "1.2.3"
260273
versions = _versions_helpers.PandasGBQVersions()
@@ -268,6 +281,7 @@ def test_installed_pandas_gbq_version_returns_parsed_version():
268281

269282
def test_installed_pandas_gbq_version_falls_back_on_import_error():
270283
import sys
284+
271285
versions = _versions_helpers.PandasGBQVersions()
272286
with mock.patch.dict(sys.modules, {"pandas_gbq": None}):
273287
version = versions.installed_version
@@ -279,6 +293,7 @@ def test_installed_pandas_gbq_version_falls_back_on_import_error():
279293

280294
def test_installed_pandas_gbq_version_falls_back_on_other_error():
281295
import sys
296+
282297
# Simulate a corrupted package raising an error on import/property access
283298
class CorruptPandasGBQ:
284299
@property
@@ -302,6 +317,7 @@ def test_pandas_gbq_delegation_api_version_returns_cached():
302317

303318
def test_pandas_gbq_delegation_api_version_returns_value():
304319
import sys
320+
305321
mock_pandas_gbq = mock.Mock()
306322
mock_pandas_gbq._internal_delegation_api_version = 42
307323
versions = _versions_helpers.PandasGBQVersions()
@@ -313,6 +329,7 @@ def test_pandas_gbq_delegation_api_version_returns_value():
313329

314330
def test_pandas_gbq_delegation_api_version_falls_back_on_import_error():
315331
import sys
332+
316333
versions = _versions_helpers.PandasGBQVersions()
317334
with mock.patch.dict(sys.modules, {"pandas_gbq": None}):
318335
version = versions.delegation_api_version
@@ -322,6 +339,7 @@ def test_pandas_gbq_delegation_api_version_falls_back_on_import_error():
322339

323340
def test_pandas_gbq_delegation_api_version_falls_back_on_other_error():
324341
import sys
342+
325343
class CorruptPandasGBQ:
326344
@property
327345
def _internal_delegation_api_version(self):

packages/google-cloud-bigquery/tests/unit/test_magics.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ def test_context_with_default_credentials():
147147
"""When Application Default Credentials are set, the context credentials
148148
will be created the first time it is called
149149
"""
150+
magics.context._credentials = None
151+
magics.context._project = None
150152
assert magics.context._credentials is None
151153
assert magics.context._project is None
152154

@@ -164,6 +166,16 @@ def test_context_with_default_credentials():
164166
assert default_mock.call_count == 2
165167

166168

169+
def test_context_fallback_when_bigquery_magics_none():
170+
ctx = magics.Context()
171+
credentials_mock = mock.create_autospec(
172+
google.auth.credentials.Credentials, instance=True
173+
)
174+
with mock.patch("google.auth.default", return_value=(credentials_mock, "proj-123")):
175+
assert ctx.credentials is credentials_mock
176+
assert ctx.project == "proj-123"
177+
178+
167179
@pytest.mark.usefixtures("ipython_interactive")
168180
@pytest.mark.skipif(pandas is None, reason="Requires `pandas`")
169181
def test_context_with_default_connection(monkeypatch):

0 commit comments

Comments
 (0)