Hello,
I have a macro called age_binning
that is essentially a CASE statement that looks like this
# age_binning.sql
{%- macro age_binning(age_column) -%}
case
when {{ age_column }} between 10 and 19 then 'val_1'
when {{ age_column }} between 20 and 29 then 'val_2'
...
end
{%- endmacro -%}
that I would like to be able to test the logic of this macro. My approach was to create a tiny model that is dedicated to testing this macro called macro_test_age_binning
# macro_test_age_binning.sql
with
dummy_data as (
select
null::int as age
)
select
dummy_data.age,
{{ age_binning('dummy_data.age') }} as age_group
from dummy_data
I created a unit_test in order to validate the macro:
-- unit_test_macro_age_binning.yaml
version: 2
unit_tests:
- name: simple_grouping
model: macro_test_age_binning
description: Ensure correct age group labels are returned
given:
- input: this
rows:
- { age: 18 }
- { age: 24 }
expect:
rows:
- { age: 18, age_group: 'val_1' }
- { age: 24, age_group: 'val_2' }
The unit test does compile and provides an output, however for some reason the table is not being overwritten by the input that I am defining in my unit test and instead keeps the NULL values that I have when initializing the testing model. This is the output of the compiled query for unit testing where I can see that it isn’t overriding the fixture data…
with
__dbt__cte__macro_test_age_binning as (
-- Fixture for macro_test_age_binning
select
cast(18 as INTEGER)
as age, cast(null as character varying(256)) as age_group
union all
select
cast(24 as INTEGER)
as age, cast(null as character varying(256)) as age_group
), dummy_data as (
select
null::int as age
)
select
dummy_data.age,
case
when dummy_data.age between 10 and 19 then 'val_1'
when dummy_data.age between 20 and 29 then 'val_2'
...
end as age_group
from dummy_data
Stop Gap Workaround
One change that I did do was to create another dummy called macro_test_age_binning_initialize
where I instead built the initial NULL data
# macro_test_age_binning_initialize.sql
select
null::int as age
and edited my model dedicated to testing the macro and changed my unit test with the following
# macro_test_age_binning.sql
select
age,
{{ age_binning('age') }} as age_group
from {{ ref('macro_test_age_binning_initialize') }}
- unit_test_macro_age_binning.yaml
version: 2
unit_tests:
- name: simple_grouping
model: macro_test_age_binning
description: Ensure correct age group labels are returned
given:
- input: ref('macro_test_age_binning_initialize') -- Changed here
rows:
- { age: 18 }
- { age: 24 }
expect:
rows:
- { age: 18, age_group: 'val_1' }
- { age: 24, age_group: 'val_2' }
With these changes, the unit test now is able to utilize the fixed dataset described in the unit test. However this workaround seems very dubious and makes testing a bit more complex with adding an extra model just to make this work..
My Question
Is this workaround actually necessary?
Is it considered a bad practice to create dedicated models for testing macros like this?
Is there a cleaner or more idiomatic way to test macros that return expressions rather than full models?
Thanks in advance!