Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

csv file fixtures #9044

Merged
merged 18 commits into from
Nov 9, 2023
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20231106-194752.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Add support of csv file fixtures to unit testing
time: 2023-11-06T19:47:52.501495-06:00
custom:
Author: emmyoop
Issue: "8290"
7 changes: 7 additions & 0 deletions core/dbt/config/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,13 @@
generic_test_paths.append(os.path.join(test_path, "generic"))
return generic_test_paths

@property
def fixture_paths(self):
fixture_paths = []
for test_path in self.test_paths:
fixture_paths.append(os.path.join(test_path, "fixtures"))
return fixture_paths

Check warning on line 660 in core/dbt/config/project.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/config/project.py#L657-L660

Added lines #L657 - L660 were not covered by tests

def __str__(self):
cfg = self.to_project_config(with_packages=True)
return str(cfg)
Expand Down
58 changes: 45 additions & 13 deletions core/dbt/contracts/graph/unparsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from io import StringIO

from dbt import deprecations
from dbt.clients.system import find_matching
from dbt.node_types import NodeType
from dbt.contracts.graph.semantic_models import (
Defaults,
Expand Down Expand Up @@ -784,42 +785,73 @@
return UnitTestFormat.Dict

@property
def rows(self) -> Union[str, List[Dict[str, Any]]]:
return []
def rows(self) -> Optional[Union[str, List[Dict[str, Any]]]]:
return None

Check warning on line 789 in core/dbt/contracts/graph/unparsed.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/unparsed.py#L789

Added line #L789 was not covered by tests

@property
def fixture(self) -> Optional[str]:
return None

Check warning on line 793 in core/dbt/contracts/graph/unparsed.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/unparsed.py#L793

Added line #L793 was not covered by tests

def get_rows(self) -> List[Dict[str, Any]]:
def get_rows(self, project_root: str, paths: List[str]) -> List[Dict[str, Any]]:
if self.format == UnitTestFormat.Dict:
assert isinstance(self.rows, List)
return self.rows
elif self.format == UnitTestFormat.CSV:
assert isinstance(self.rows, str)
dummy_file = StringIO(self.rows)
reader = csv.DictReader(dummy_file)
rows = []
for row in reader:
rows.append(row)
if self.fixture is not None:
assert isinstance(self.fixture, str)
file_path = self.get_fixture_path(self.fixture, project_root, paths)
with open(file_path, newline="") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
rows.append(row)

Check warning on line 807 in core/dbt/contracts/graph/unparsed.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/unparsed.py#L801-L807

Added lines #L801 - L807 were not covered by tests
else: # using inline csv
assert isinstance(self.rows, str)
dummy_file = StringIO(self.rows)
reader = csv.DictReader(dummy_file)
rows = []
for row in reader:
rows.append(row)

Check warning on line 814 in core/dbt/contracts/graph/unparsed.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/unparsed.py#L809-L814

Added lines #L809 - L814 were not covered by tests
return rows

def get_fixture_path(self, fixture: str, project_root: str, paths: List[str]) -> str:
fixture_path = f"{fixture}.csv"
matches = find_matching(project_root, paths, fixture_path)
if len(matches) == 0:
raise ParsingError(f"Could not find fixture file {fixture} for unit test")
elif len(matches) > 1:
raise ParsingError(

Check warning on line 823 in core/dbt/contracts/graph/unparsed.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/unparsed.py#L818-L823

Added lines #L818 - L823 were not covered by tests
f"Found multiple fixture files named {fixture} at {[d['relative_path'] for d in matches]}. Please use a unique name for each fixture file."
)

return matches[0]["relative_path"]

Check warning on line 827 in core/dbt/contracts/graph/unparsed.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/unparsed.py#L827

Added line #L827 was not covered by tests

def validate_fixture(self, fixture_type, test_name) -> None:
if (self.format == UnitTestFormat.Dict and not isinstance(self.rows, list)) or (
self.format == UnitTestFormat.CSV and not isinstance(self.rows, str)
):
if self.format == UnitTestFormat.Dict and not isinstance(self.rows, list):
raise ParsingError(
f"Unit test {test_name} has {fixture_type} rows which do not match format {self.format}"
)
if self.format == UnitTestFormat.CSV and not (
isinstance(self.rows, str) or isinstance(self.fixture, str)
):
raise ParsingError(

Check warning on line 837 in core/dbt/contracts/graph/unparsed.py

View check run for this annotation

Codecov / codecov/patch

core/dbt/contracts/graph/unparsed.py#L837

Added line #L837 was not covered by tests
f"Unit test {test_name} has {fixture_type} rows or fixtures which do not match format {self.format}. Expected string."
)


@dataclass
class UnitTestInputFixture(dbtClassMixin, UnitTestFixture):
input: str
rows: Union[str, List[Dict[str, Any]]] = ""
rows: Optional[Union[str, List[Dict[str, Any]]]] = None
format: UnitTestFormat = UnitTestFormat.Dict
fixture: Optional[str] = None


@dataclass
class UnitTestOutputFixture(dbtClassMixin, UnitTestFixture):
rows: Union[str, List[Dict[str, Any]]] = ""
rows: Optional[Union[str, List[Dict[str, Any]]]] = None
format: UnitTestFormat = UnitTestFormat.Dict
fixture: Optional[str] = None


@dataclass
Expand Down
10 changes: 8 additions & 2 deletions core/dbt/parser/unit_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ def parse_unit_test_case(self, test_case: UnitTestDefinition):
original_file_path=test_case.original_file_path,
unique_id=test_case.unique_id,
config=UnitTestNodeConfig(
materialized="unit", expected_rows=test_case.expect.get_rows()
materialized="unit",
expected_rows=test_case.expect.get_rows(
self.root_project.project_root, self.root_project.fixture_paths
),
),
raw_code=tested_node.raw_code,
database=tested_node.database,
Expand Down Expand Up @@ -122,7 +125,10 @@ def parse_unit_test_case(self, test_case: UnitTestDefinition):
input_unique_id = f"model.{package_name}.{input_name}"
input_node = ModelNode(
raw_code=self._build_fixture_raw_code(
given.get_rows(), original_input_node_columns
given.get_rows(
self.root_project.project_root, self.root_project.fixture_paths
),
original_input_node_columns,
),
resource_type=NodeType.Model,
package_name=package_name,
Expand Down
Loading
Loading