<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Bicep Console on Andrew Wilson's Blog</title><link>https://andrewilson.co.uk/tags/bicep-console/</link><description>Recent content in Bicep Console on Andrew Wilson's Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sat, 09 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://andrewilson.co.uk/tags/bicep-console/index.xml" rel="self" type="application/rss+xml"/><item><title>Unit Testing Bicep Logic with BicepConsoleTTK</title><link>https://andrewilson.co.uk/post/2026/05/bicepconsolettk/</link><pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate><guid>https://andrewilson.co.uk/post/2026/05/bicepconsolettk/</guid><description>Problem Space In most Infrastructure as Code teams, Bicep quality checks start to look mature as soon as linting and deployment validation are in place. In practice, there is still a blind spot: logic-level testing of exported functions, types, and variables.
Most teams validate by deploying to a subscription and checking outcomes afterwards. That is useful, but it is also slow and expensive when what you actually want to verify is pure logic.</description><content:encoded><![CDATA[<h2 id="problem-space">Problem Space</h2>
<p>In most Infrastructure as Code teams, Bicep quality checks start to look mature as soon as linting and deployment validation are in place. In practice, there is still a blind spot: logic-level testing of exported functions, types, and variables.</p>
<p>Most teams validate by deploying to a subscription and checking outcomes afterwards. That is useful, but it is also slow and expensive when what you actually want to verify is pure logic. A shared Bicep library changes, everything still compiles, and then a downstream module fails later in a deployment pipeline because a naming function or constructor behaviour subtly changed. That is exactly the type of issue we normally catch early in application development with unit tests.</p>
<p>If you maintain shared Bicep libraries, this gap hurts quickly:</p>
<ul>
<li>Naming functions drift without anyone noticing</li>
<li>Type constructors change and break downstream modules</li>
<li>Refactors feel risky because feedback loops are too long</li>
</ul>
<p>I wanted a way to unit test Bicep logic directly, without deploying anything.</p>
<h2 id="introducing-bicepconsolettk">Introducing BicepConsoleTTK</h2>
<p>To solve that problem, I created <strong>BicepConsoleTTK</strong> (Bicep Console Test Tool Kit).</p>
<p>It is a Pester-based framework that executes Bicep expressions through the Bicep console REPL, so you can assert outputs in fast, repeatable unit tests.</p>
<p>Repository:</p>
<p><a href="https://github.com/Andrew-D-Wilson/bicep-console-test-framework">
  <img src="https://img.shields.io/badge/GitHub-Bicep--Console--TTK-181717?logo=github" alt="GitHub Repository">

</a>
<a href="https://www.powershellgallery.com/packages/BicepConsoleTTK">
  <img src="https://img.shields.io/powershellgallery/v/BicepConsoleTTK?logo=powershell&amp;label=PowerShell%20Gallery" alt="PowerShell Gallery">

</a></p>
<p>At a high level, the toolkit gives you two commands:</p>
<ul>
<li><code>Import-Bicep</code>: reads one or more Bicep files and extracts the exports you ask for</li>
<li><code>Invoke-BicepExpression</code>: runs those declarations plus your expression in <code>bicep console</code> and returns the result</li>
</ul>
<h2 id="why-this-approach-works">Why This Approach Works</h2>
<p>This lets you test Bicep logic in isolation, before template deployment.</p>
<p>Practical outcomes:</p>
<ul>
<li>Faster feedback during development</li>
<li>More confidence when refactoring shared functions</li>
<li>Cleaner CI pipelines for template libraries</li>
<li>Better separation between unit tests (logic) and integration tests (deployment)</li>
</ul>
<h2 id="features">Features</h2>
<ul>
<li>Familiar import syntax (named imports and wildcard imports)</li>
<li>Multi-file import composition with preserved order</li>
<li>Deduplication when the same member is imported multiple ways</li>
<li>Setup declarations for multi-step scenarios</li>
<li>Pipeline input support</li>
<li>Cleaner Bicep console error reporting</li>
<li>CI/CD-friendly, non-interactive execution</li>
</ul>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>PowerShell 5.1 (Desktop) or 7+</li>
<li>Pester 5.x</li>
<li>Bicep CLI 0.42.1+</li>
<li><code>bicep</code> available on your <code>PATH</code></li>
</ul>
<h2 id="installation">Installation</h2>
<h3 id="from-powershell-gallery">From PowerShell Gallery</h3>
<p><a href="https://www.powershellgallery.com/packages/BicepConsoleTTK">
  <img src="https://img.shields.io/powershellgallery/v/BicepConsoleTTK?logo=powershell&amp;label=PowerShell%20Gallery" alt="PowerShell Gallery">

</a></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">Install-Module</span> -Name BicepConsoleTTK -Repository PSGallery
</span></span></code></pre></div><p>In your test file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>BeforeAll {
</span></span><span style="display:flex;"><span>	<span style="color:#8be9fd;font-style:italic">Import-Module</span> BicepConsoleTTK -Force
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="from-source">From Source</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>git clone https://github.com/<span style="color:#8be9fd;font-style:italic">Andrew-D</span>-Wilson/<span style="color:#8be9fd;font-style:italic">bicep-console</span>-test-framework.git
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">Import-Module</span> <span style="color:#f1fa8c">&#34;</span><span style="color:#8be9fd;font-style:italic">$PSScriptRoot</span><span style="color:#f1fa8c">/../src/BicepConsoleTTK&#34;</span> -Force
</span></span></code></pre></div><h2 id="copilot-integration">Copilot Integration</h2>
<p>This repository ships two complementary artefacts that teach GitHub Copilot how to write BicepConsoleTTK tests.</p>
<h3 id="agent-skill-recommended">Agent Skill (Recommended)</h3>
<p>The <code>.github/skills/bicepconsolettk/SKILL.md</code> file is a GitHub Copilot agent skill. When Copilot is working in agent mode, it automatically loads this skill whenever the task is related to writing Bicep tests, injecting the full authoring guide into its context.</p>
<p>The skill is discovered automatically from <code>.github/skills/</code> in any repository that contains it.</p>
<h3 id="vs-code-instructions-file">VS Code Instructions File</h3>
<p>The <code>bicepconsolettk.instructions.md</code> file is a VS Code Copilot custom instructions file.</p>
<p>To use it in your own repository:</p>
<ol>
<li>Copy the file to <code>.github/instructions/bicepconsolettk.instructions.md</code></li>
<li>Keep <code>applyTo: &quot;**/*.Tests.ps1&quot;</code> so it is scoped to Pester test files</li>
<li>Ask Copilot to generate or refactor tests in your <code>*.Tests.ps1</code> files</li>
</ol>
<p>With both artefacts in place, Copilot gets better guidance in both agent-driven workflows and editor-scoped instruction workflows.</p>
<h2 id="usage">Usage</h2>
<h3 id="1-import-exports-from-bicep-files">1. Import exports from Bicep files</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$imports</span> = <span style="color:#8be9fd;font-style:italic">Import-Bicep</span> @(
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;import {coreParams, newCoreParams} from &#39;</span><span style="color:#8be9fd;font-style:italic">$PSScriptRoot</span><span style="color:#f1fa8c">/../shared/Types.bicep&#39;&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;import {basicResource}             from &#39;</span><span style="color:#8be9fd;font-style:italic">$PSScriptRoot</span><span style="color:#f1fa8c">/../shared/NamingFunctions.bicep&#39;&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="2-evaluate-an-expression">2. Evaluate an expression</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$result</span> = <span style="color:#8be9fd;font-style:italic">Invoke-BicepExpression</span> -b <span style="color:#8be9fd;font-style:italic">$imports</span> -e <span style="color:#f1fa8c">&#34;newCoreParams(&#39;uksouth&#39;, &#39;uks&#39;, &#39;prod&#39;, &#39;myapp&#39;)&#34;</span>
</span></span></code></pre></div><h3 id="3-use-setup-declarations-when-needed">3. Use setup declarations when needed</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$setupDeclarations</span> = @(
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;var projectNameStart = &#39;hello&#39;&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;var projectNameComplete = &#39;</span><span style="color:#f1fa8c">`$</span><span style="color:#f1fa8c">{projectNameStart}world&#39;&#34;</span>,
</span></span><span style="display:flex;"><span>	<span style="color:#f1fa8c">&#34;var coreParameters coreParams = newCoreParams(&#39;uksouth&#39;, &#39;uks&#39;, &#39;dev&#39;, projectNameComplete)&#34;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">$result</span> = <span style="color:#8be9fd;font-style:italic">Invoke-BicepExpression</span> -b <span style="color:#8be9fd;font-style:italic">$imports</span> -s <span style="color:#8be9fd;font-style:italic">$setupDeclarations</span> -e <span style="color:#f1fa8c">&#34;basicResource(&#39;aks&#39;, coreParameters)&#34;</span>
</span></span></code></pre></div><h2 id="example-pester-test">Example Pester Test</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span>BeforeAll {
</span></span><span style="display:flex;"><span>	<span style="color:#8be9fd;font-style:italic">Import-Module</span> BicepConsoleTTK -Force
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Describe <span style="color:#f1fa8c">&#34;Naming Functions&#34;</span> {
</span></span><span style="display:flex;"><span>	BeforeAll {
</span></span><span style="display:flex;"><span>		<span style="color:#8be9fd;font-style:italic">$script:imports</span> = <span style="color:#8be9fd;font-style:italic">Import-Bicep</span> @(
</span></span><span style="display:flex;"><span>			<span style="color:#f1fa8c">&#34;import {coreParams, newCoreParams} from &#39;</span><span style="color:#8be9fd;font-style:italic">$PSScriptRoot</span><span style="color:#f1fa8c">/../shared/Types.bicep&#39;&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#f1fa8c">&#34;import {basicResource, csResource} from &#39;</span><span style="color:#8be9fd;font-style:italic">$PSScriptRoot</span><span style="color:#f1fa8c">/../shared/NamingFunctions.bicep&#39;&#34;</span>
</span></span><span style="display:flex;"><span>		)
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	It <span style="color:#f1fa8c">&#34;basicResource includes abbreviation, project, environment and location&#34;</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#8be9fd;font-style:italic">$result</span> = <span style="color:#8be9fd;font-style:italic">Invoke-BicepExpression</span> -b <span style="color:#8be9fd;font-style:italic">$script:imports</span> `
</span></span><span style="display:flex;"><span>			-e <span style="color:#f1fa8c">&#34;basicResource(&#39;aks&#39;, newCoreParams(&#39;uksouth&#39;, &#39;uks&#39;, &#39;dev&#39;, &#39;myapp&#39;))&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#8be9fd;font-style:italic">$result</span> | Should -Be <span style="color:#f1fa8c">&#34;&#39;aks-myapp-dev-uksouth&#39;&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Run tests with:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-powershell" data-lang="powershell"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">Invoke-Pester</span> -Path ./tests
</span></span></code></pre></div><h2 id="how-it-works-short-version">How It Works (Short Version)</h2>
<p><code>Import-Bicep</code> parses import strings, resolves file paths, and extracts exported declarations from source files.</p>
<p><code>Invoke-BicepExpression</code> sends those declarations and your expression into <code>bicep console</code>, captures output, and translates console noise into readable exceptions when failures occur.</p>
<p>That gives you deterministic, fast unit tests focused on logic instead of deployment orchestration.</p>
<h2 id="where-this-fits-in-your-testing-strategy">Where This Fits in Your Testing Strategy</h2>
<p>Use BicepConsoleTTK for:</p>
<ul>
<li>Function output verification</li>
<li>Naming convention enforcement</li>
<li>Shared type constructor validation</li>
<li>Regression checks during refactoring</li>
</ul>
<p>Still keep deployment/integration tests for:</p>
<ul>
<li>Resource runtime behavior</li>
<li>Policy and RBAC interactions</li>
<li>End-to-end environment validation</li>
</ul>
<p>Both layers matter. This tool improves the unit-testing layer.</p>
<h2 id="in-short">In Short</h2>
<p>BicepConsoleTTK helps you test Bicep exports the same way you test application code: quickly, repeatedly, and early.</p>
<p>If your team relies on shared Bicep libraries, this can remove a lot of friction from your delivery pipeline while increasing confidence in every change.</p>
]]></content:encoded></item></channel></rss>