Skip to content

Commit a5b4e63

Browse files
authored
Ability to perform unit tests on models with dependencies - fixing ref() macro (#293)
### Summary Fixing our custom `ref()` macro in order to support unit testing on models with dependencies. ### Description This is originated from the error we were getting while trying to perform unit tests on models with dependencies → `Compilation Error ... 'dict object' has no attribute 'nodes'`. Our original override of `ref()` macro relies on `graph` Jinja function in some specific cases to retrieve the metadata of a model. However, this `graph` is not available (with content) during unit tests, but it's essential for other cases (e.g. **reflection** models). Therefore, we added a check before its usage and enabled `ref()` on unit testing models. With this fix, we basically are using the builtin `ref()`'s result. ### Test Results - Refactored tests for unit testing - All tests are passing ### Changelog - [x] Added a summary of what this PR accomplishes to CHANGELOG.md
1 parent 24a8512 commit a5b4e63

4 files changed

Lines changed: 41 additions & 184 deletions

File tree

.github/expected_failures.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ tests/component/test_profile_template.py::TestProfileTemplate::test_cloud_option
22
tests/functional/adapter/dremio_specific/test_drop_temp_table.py::TestDropTempTableDremio::test_drop_temp_table
33
tests/functional/adapter/dremio_specific/test_schema_parsing.py::TestSchemaParsingDremio::test_schema_with_dots
44
tests/functional/adapter/dremio_specific/test_verify_ssl.py::TestVerifyCertificateDremio::test_insecure_request_warning_not_exist
5-
tests/functional/adapter/unit_testing/test_unit_testing.py::TestDremioUnitTestCaseInsensitivity::test_case_insensitivity
5+
tests/functional/adapter/unit_testing/test_unit_testing.py::TestDremioUnitTestingTypes::test_unit_test_data_type
66
tests/hooks/test_model_hooks.py::TestPrePostModelHooksInConfigDremio::test_pre_and_post_model_hooks_model
77
tests/hooks/test_model_hooks.py::TestPrePostModelHooksInConfigWithCountDremio::test_pre_and_post_model_hooks_model_and_project
88
tests/hooks/test_model_hooks.py::TestPrePostModelHooksInConfigKwargsDremio::test_pre_and_post_model_hooks_model

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
## Changes
44

5-
- Basic unit testing support
6-
- Only tables and views without dependencies, i.e., without using Jinja function ref(), can be tested
5+
- Unit testing feature with limitation
76
- Not all data types / castings are supported
87
- Included dbt-dremio version in query metadata
98
- Adjusted existing pipeline scripts to support Jenkins
109

1110
## Features
1211

13-
- [#291](https://github.com/dremio/dbt-dremio/pull/291) dbt Unit Testing - basic implementation
12+
- [#291](https://github.com/dremio/dbt-dremio/pull/291) [#293](https://github.com/dremio/dbt-dremio/pull/293) dbt Unit Testing
13+
1414
# dbt-dremio v1.8.3
1515

1616
## Changes

dbt/include/dremio/macros/builtins/builtins.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ limitations under the License.*/
1414

1515
{%- macro ref(model_name, v=None) -%}
1616
{%- set relation = builtins.ref(model_name, v=v) -%}
17-
{%- if execute -%}
17+
{%- if execute and graph -%}
1818
{%- set model = graph.nodes.values() | selectattr("name", "equalto", model_name) | list | first -%}
1919
{%- if model.config.materialized == 'reflection' -%}
2020
{% do exceptions.CompilationError("Reflections cannot be ref()erenced (" ~ relation ~ ")") %}

tests/functional/adapter/unit_testing/test_unit_testing.py

Lines changed: 36 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -19,39 +19,7 @@
1919
)
2020
from dbt.tests.adapter.unit_testing.test_invalid_input import BaseUnitTestInvalidInput
2121

22-
from dbt.tests.util import write_file, run_dbt, run_dbt_and_capture
23-
24-
my_model_sql = """
25-
select
26-
tested_column
27-
from (
28-
select
29-
{sql_value} as tested_column
30-
)
31-
"""
32-
33-
test_my_model_yml = """
34-
unit_tests:
35-
- name: test_my_model
36-
model: my_model
37-
given:
38-
- input: this
39-
rows:
40-
- {{ tested_column: {yaml_value} }}
41-
expect:
42-
rows:
43-
- {{ tested_column: {yaml_value} }}
44-
"""
45-
4622
class TestDremioUnitTestingTypes(BaseUnitTestingTypes):
47-
# TODO: Revisit once we support unit testing with models that have dependencies
48-
@pytest.fixture(scope="class")
49-
def models(self):
50-
return {
51-
"my_model.sql": my_model_sql,
52-
"schema.yml": test_my_model_yml
53-
}
54-
5523
@pytest.fixture
5624
def data_types(self):
5725
# https://docs.dremio.com/current/reference/sql/data-types/
@@ -60,165 +28,54 @@ def data_types(self):
6028
["1", "1"],
6129
["'1'", "1"],
6230
["cast('true' as boolean)", "true"],
63-
# ["1.0", "1.0"], # FIXME: Revisit, wrong fixture
31+
["1.0", "1.0"], # FIXME: Revisit, wrong fixture
6432
["'string value'", "string value"],
65-
# ["cast(1.0 as numeric)", "1.0"], # FIXME: Revisit, wrong fixture
33+
["cast(1.0 as numeric)", "1.0"], # FIXME: Revisit, wrong fixture
6634
["cast(1 as bigint)", 1],
6735
["cast('2019-01-01' as date)", "2019-01-01"],
68-
# ["cast('2013-11-03 00:00:00' as timestamp)", "2013-11-03 00:00:00"], #FIXME: Revisit, wrong fixture
69-
# ["st_geogpoint(75, 45)", "'st_geogpoint(75, 45)'"], # ?
36+
["cast('2013-11-03 00:00:00' as timestamp)", "2013-11-03 00:00:00"], #FIXME: Revisit, wrong fixture
37+
["st_geogpoint(75, 45)", "'st_geogpoint(75, 45)'"], # ?
7038
# arrays #FIXME: Revisit, wrong fixture
71-
# ["array['a','b','c']", "['a','b','c']"],
72-
# ["array[1,2,3]", "[1,2,3]"],
73-
# ["array[true,true,false]", "[true,true,false]"],
39+
["array['a','b','c']", "['a','b','c']"],
40+
["array[1,2,3]", "[1,2,3]"],
41+
["array[true,true,false]", "[true,true,false]"],
7442
# array of date #FIXME: Revisit, wrong fixture
75-
# ["array[date '2019-01-01']", "['2020-01-01']"],
76-
# ["array[date '2019-01-01']", "[]"],
77-
# ["array[date '2019-01-01']", "null"],
43+
["array[date '2019-01-01']", "['2020-01-01']"],
44+
["array[date '2019-01-01']", "[]"],
45+
["array[date '2019-01-01']", "null"],
7846
# array of timestamp #FIXME: Revisit, wrong fixture
79-
# ["array[timestamp '2019-01-01']", "['2020-01-01']"],
80-
# ["array[timestamp '2019-01-01']", "[]"],
81-
# ["array[timestamp '2019-01-01']", "null"],
47+
["array[timestamp '2019-01-01']", "['2020-01-01']"],
48+
["array[timestamp '2019-01-01']", "[]"],
49+
["array[timestamp '2019-01-01']", "null"],
8250
# json
83-
# [
84-
# """json '{"name": "Cooper", "forname": "Alice"}'""",
85-
# """{"name": "Cooper", "forname": "Alice"}""",
86-
# ],
87-
# ["""json '{"name": "Cooper", "forname": "Alice"}'""", "{}"],
51+
[
52+
"""json '{"name": "Cooper", "forname": "Alice"}'""",
53+
"""{"name": "Cooper", "forname": "Alice"}""",
54+
],
55+
["""json '{"name": "Cooper", "forname": "Alice"}'""", "{}"],
8856
# structs
89-
# [
90-
# "struct('Isha' as name, 22 as age)",
91-
# """'struct("Isha" as name, 22 as age)'""",
92-
# ],
93-
# [
94-
# "struct('Kipketer' AS name, [23.2, 26.1, 27.3, 29.4] AS laps)",
95-
# """'struct("Kipketer" AS name, [23.2, 26.1, 27.3, 29.4] AS laps)'""",
96-
# ],
57+
[
58+
"struct('Isha' as name, 22 as age)",
59+
"""'struct("Isha" as name, 22 as age)'""",
60+
],
61+
[
62+
"struct('Kipketer' AS name, [23.2, 26.1, 27.3, 29.4] AS laps)",
63+
"""'struct("Kipketer" AS name, [23.2, 26.1, 27.3, 29.4] AS laps)'""",
64+
],
9765
# struct of struct
98-
# [
99-
# "struct(struct(1 as id, 'blue' as color) as my_struct)",
100-
# """'struct(struct(1 as id, "blue" as color) as my_struct)'""",
101-
# ],
66+
[
67+
"struct(struct(1 as id, 'blue' as color) as my_struct)",
68+
"""'struct(struct(1 as id, "blue" as color) as my_struct)'""",
69+
],
10270
# array of struct
103-
# [
104-
# "[struct(st_geogpoint(75, 45) as my_point), struct(st_geogpoint(75, 35) as my_point)]",
105-
# "['struct(st_geogpoint(75, 45) as my_point)', 'struct(st_geogpoint(75, 35) as my_point)']",
106-
# ],
71+
[
72+
"[struct(st_geogpoint(75, 45) as my_point), struct(st_geogpoint(75, 35) as my_point)]",
73+
"['struct(st_geogpoint(75, 45) as my_point)', 'struct(st_geogpoint(75, 35) as my_point)']",
74+
],
10775
]
10876

109-
def test_unit_test_data_type(self, project, data_types):
110-
for sql_value, yaml_value in data_types:
111-
# Write parametrized type value to sql files
112-
write_file(
113-
my_model_sql.format(sql_value=sql_value),
114-
"models",
115-
"my_model.sql",
116-
)
117-
118-
# Write parametrized type value to unit test yaml definition
119-
write_file(
120-
test_my_model_yml.format(yaml_value=yaml_value),
121-
"models",
122-
"schema.yml",
123-
)
124-
125-
results = run_dbt(["run", "--select", "my_model"])
126-
assert len(results) == 1
127-
128-
try:
129-
run_dbt(["test", "--select", "my_model"])
130-
except Exception:
131-
raise AssertionError(f"unit test failed when testing model with {sql_value}")
132-
133-
134-
my_model_2_sql = """
135-
select
136-
tested_column
137-
from (
138-
select
139-
1 as tested_column
140-
)
141-
"""
142-
143-
test_my_model_2_yml = """
144-
unit_tests:
145-
- name: test_my_model
146-
model: my_model_2
147-
given:
148-
- input: this
149-
rows:
150-
- {tested_column: 1}
151-
- {TESTED_COLUMN: 2}
152-
- {tested_colUmn: 3}
153-
expect:
154-
rows:
155-
- {tested_column: 1}
156-
- {TESTED_COLUMN: 2}
157-
- {tested_colUmn: 3}
158-
"""
159-
16077
class TestDremioUnitTestCaseInsensitivity(BaseUnitTestCaseInsensivity):
161-
# FIXME: Revisit
162-
@pytest.fixture(scope="class")
163-
def models(self):
164-
return {
165-
"my_model_2.sql": my_model_2_sql,
166-
"unit_tests_2.yml": test_my_model_2_yml,
167-
}
168-
169-
def test_case_insensitivity(self, project):
170-
results = run_dbt(["run"])
171-
assert len(results) == 1
172-
173-
results = run_dbt(["test"])
174-
175-
176-
test_my_model_3_yml = """
177-
unit_tests:
178-
- name: test_invalid_input_column_name
179-
model: my_model_2
180-
given:
181-
- input: this
182-
rows:
183-
- {invalid_column_name: 1}
184-
expect:
185-
rows:
186-
- {tested_column: 1}
187-
- name: test_invalid_expect_column_name
188-
model: my_model_2
189-
given:
190-
- input: this
191-
rows:
192-
- {tested_column: 1}
193-
expect:
194-
rows:
195-
- {invalid_column_name: 1}
196-
"""
78+
pass
19779

19880
class TestDremioUnitTestInvalidInput(BaseUnitTestInvalidInput):
199-
@pytest.fixture(scope="class")
200-
def models(self):
201-
return {
202-
"my_model_2.sql": my_model_2_sql,
203-
"unit_tests_3.yml": test_my_model_3_yml
204-
}
205-
206-
def test_invalid_input(self, project):
207-
results = run_dbt(["run"])
208-
assert len(results) == 1
209-
210-
_, out = run_dbt_and_capture(
211-
["test", "--select", "test_name:test_invalid_input_column_name"], expect_pass=False
212-
)
213-
assert (
214-
"Invalid column name: 'invalid_column_name' in unit test fixture for 'my_model_2'."
215-
in out
216-
)
217-
218-
_, out = run_dbt_and_capture(
219-
["test", "--select", "test_name:test_invalid_expect_column_name"], expect_pass=False
220-
)
221-
assert (
222-
"Invalid column name: 'invalid_column_name' in unit test fixture for expected output."
223-
in out
224-
)
81+
pass

0 commit comments

Comments
 (0)