現時点(2024年9月)では、dbt-coreのunittestではsql_headerが使えないので、
その課題に対するmonkeypatchを当ててみました。

[Feature] Make sql_header configuration available on tests | dbt-labs/dbt-core | GitHub
https://github.com/dbt-labs/dbt-core/issues/9775

特にBigQueryを使っている場合は、
sql_headerに一時UDFを定義することになるので、
テストできるモデルが制限されてしまいます。

Create a BigQuery Temporary UDF | sql_header | docs.getdbt.com
https://docs.getdbt.com/reference/resource-configs/sql_header#create-a-bigquery-temporary-udf

sql_headerに一時UDFを定義するという仕様自体がworkaroundっぽい気もするので、
とりあえずmonkeypatchを当てて対応してみることにしました。

パッチ一覧

作成したパッチの一覧です。

patch for bigquery unittest with sql_header | takemikami/dbt-core | GitHub
https://github.com/takemikami/dbt-core/commit/d329c152f036b9249422f77acd0432091d2c88ac

patch for bigquery unittest with sql_header | takemikami/dbt-adapters | GitHub
https://github.com/takemikami/dbt-adapters/commit/780acadfcf363c0928b742ba1db6bded2bc89325

ざっくり言うと、
dbt-core側で、jinjaのcontextにsql_headerの内容を渡し、
dbt-adapters側の、jinjaテンプレートでsql_headerをrenderしています。

使い方

以前のエントリで試したBigQueryEmulatorを使う前提で、使い方の流れを示します。

BigQueryEmulatorを使ってdbt-coreを動かす検討(monkeypatchを当てて) | takemikami.com
https://takemikami.com/2024/0916-sqlfluff-dbt-bq-emulator.html

dbtのプロジェクトを作る

dbtのプロジェクトを作ります。
Python仮想環境を用意して、パッチを適用したdbt-coreをインストールします。

$ mkdir dbt-bqpatch-study && cd $_
$ python -m venv venv
$ . venv/bin/activate
$ pip install git+https://github.com/takemikami/dbt-core@d329c152f036b9249422f77acd0432091d2c88ac#subdirectory=core git+https://github.com/takemikami/dbt-adapters@780acadfcf363c0928b742ba1db6bded2bc89325 git+https://github.com/takemikami/dbt-bigquery.git@4c84de2e72e876b3c4ed0307c36cfb5a97ac4e75

dbtを初期化します。
※project, datasetはemulator起動時のオプションと揃える。

$ dbt init study
12:08:14  Running with dbt=1.9.0-a1
12:08:14
Your new dbt project "study" was created!

For more information on how to configure the profiles.yml file,
please consult the dbt documentation here:

  https://docs.getdbt.com/docs/configure-your-profile

One more thing:

Need help? Don't hesitate to reach out to us via GitHub issues or on Slack:

  https://community.getdbt.com/

Happy modeling!

12:08:14  Setting up your profile.
Which database would you like to use?
[1] bigquery

(Don't see the one you want? https://docs.getdbt.com/docs/available-adapters)

Enter a number: 1
[1] oauth
[2] service_account
Desired authentication method option (enter a number): 1
project (GCP project id): test
dataset (the name of your dbt dataset): dataset1
threads (1 or more): 1
job_execution_timeout_seconds [300]:
[1] US
[2] EU
Desired location option (enter a number): 1

BigQuery Emulatorへの接続を設定する

~/.dbt/profiles.ymlを、次のように編集します。
※methodの変更、api_endpointを追加

study:
  outputs:
    dev:
      dataset: dataset1
      job_execution_timeout_seconds: 300
      job_retries: 1
      location: US
      method: emulator
      priority: interactive
      project: test
      threads: 1
      type: bigquery
      api_endpoint: http://127.0.0.1:9050
  target: dev

dbt debugで接続OKとなることを確認します。

$ cd study
$ dbt debug

一時UDFを追加してunittestを実行する

サンプルのモデルを、一時UDFを使ったコードに変更します。

models/example/my_first_dbt_model.sql

/*
    Welcome to your first dbt model!
    Did you know that you can also configure models directly within SQL files?
    This will override configurations stated in dbt_project.yml

    Try changing "table" to "view" below
*/

{{ config(materialized='table') }}
{% call set_sql_header(config) %}
create temp function add(x1 int64, x2 int64)
returns int64
as (
  x1 + x2
);
{% endcall %}

with source_data as (

    select add(1, 0) as id
    union all
    select add(1, 1) as id

)

select *
from source_data

/*
    Uncomment the line below to remove records with null `id` values
*/

-- where id is not null

unittestを追加します。

study/models/example/unittest.yml

unit_tests:
  - name: my_unit_test
    model: my_first_dbt_model
    given: []
    expect:
      rows:
        - {id: 1}
        - {id: 2}

unittestを実行して、成功することを確認します。

$ dbt test --select unit_test:study.my_unit_test
12:31:06  Running with dbt=1.9.0-a1
12:31:07  Registered adapter: bigquery=1.8.2
12:31:07  Found 2 models, 4 data tests, 481 macros, 1 unit test
12:31:07
12:31:07  Concurrency: 1 threads (target='dev')
12:31:07
12:31:07  1 of 1 START unit_test my_first_dbt_model::my_unit_test ........................ [RUN]
12:31:07  1 of 1 PASS my_first_dbt_model::my_unit_test ................................... [PASS in 0.36s]
12:31:07
12:31:07  Finished running 1 unit test in 0 hours 0 minutes and 0.39 seconds (0.39s).
12:31:07
12:31:07  Completed successfully
12:31:07
12:31:07  Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1

以上。