All automated tests live in src/Dax.Template.Tests/ (xUnit, net10.0).
Infrastructure/OfflineModelFixture.cs— builds a small, synthetic, disconnected TOMDatabasein memory (aSalestable with a date column and measures, anOrderstable with a second date column). Because theDatabaseis never attached to aServer, the model is disconnected — this is what makesEngine.RequestTableRefresh's guard skip refresh requests (see apply-templates-lifecycle.md), so the wholeApplyTemplatespath runs without any live Analysis Services / Power BI connection.Infrastructure/GoldenFile.cs— serializes the resultingDatabaseto BIM JSON (SerializeNormalized), normalizes non-deterministic content (freshly-generatedlineageTagGUIDs are blanked out, line endings normalized), and compares against a committed snapshot file under_data/Golden/*.bim(AssertMatchesSnapshot).- Set the environment variable
UPDATE_GOLDEN=1to (re)write the snapshot files instead of asserting against them — used when a template change intentionally changes the generated model. - Test entry points:
PackageTests.cs,ApplyTemplatesGoldenTests.cs,HierarchyTabularReferenceTests.cs.
Infrastructure/LiveServerFactAttribute.cs— aFactAttributethat self-skips unless bothDAXTEMPLATE_LIVE_SERVERandDAXTEMPLATE_LIVE_DATABASEenvironment variables are set. Tests using[LiveServerFact]stay discoverable/runnable on demand but never gate the pipeline.- CI (.github/workflows/ci.yml) runs
dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csprojwith no filter; live-server tests simply report as skipped rather than failing, because the env vars are unset in CI. - The three
[LiveServerFact]tests —CalendarStandardConfig_LiveServerApply_ProducesModelChanges(CalendarGoldenTests.cs),CalculationGroupStandardConfig_LiveServerApply_ProducesModelChanges(CalculationGroupGoldenTests.cs), andFunctionLibraryStandardConfig_LiveServerApply_ProducesModelChanges(FunctionLibraryGoldenTests.cs) — all target the same committed empty model, pbix/EmptyTestTemplates.pbix. Publish/deploy it to a Tabular server (Power BI Premium/PPU/Fabric XMLA read-write endpoint, or a recent Analysis Services instance) and pointDAXTEMPLATE_LIVE_SERVERat that endpoint andDAXTEMPLATE_LIVE_DATABASEat the deployed model's name. - An empty model is sufficient — no tables need to be pre-created — because each config is self-contained: Functions (
Config-04 - Functions.template.json) is model-level and its UDF bodies reference only their own parameters; calculation groups (Config-03 - CalculationGroup.template.json) generate their ownTime Intelligencetable and store calculation-item DAX as opaque, never-compiled strings; and Calendar (Config-02 - Calendar.template.json) first generates its ownDatetable viaCustomDateTable, then binds theCalendarcolumn groups to the columns it just created. - Each test runs
Engine.ApplyTemplates+Engine.GetModelChangesand deliberately never callsSaveChanges, so the run is non-destructive — changes are discarded on disconnect. - The deployed model must report
CompatibilityLevel >= 1702(required for user-defined functions); the Functions test throwsInvalidConfigurationExceptionon its compat pre-check otherwise. It is also the only one of the three that actually validates/compiles the generated(params) => bodyDAX signatures server-side, since TOM does not validateFunction.Expressionoffline. - To run only these tests:
dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csproj --filter "FullyQualifiedName~LiveServer"(with the two env vars set).
src/Dax.Template/AssemblyInfo.cs declares [InternalsVisibleTo("Dax.Template.Tests")] (with a signed-build variant under the SIGNED compilation symbol, matching the Azure DevOps release pipeline's assembly signing).
This lets test code assert on internal state — notably the Tabular* back-references described in table-generation.md — without needing a public API surface just for testing.
- All offline tests:
dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csproj --configuration Release - Single test:
dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csproj --filter "FullyQualifiedName~<TestName>" - Explicitly excluding live-server tests: add
--filter "FullyQualifiedName!~LiveServer"(not required, since they self-skip without the env vars, but useful to avoid the "skipped" noise).