<?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="ai-led-test-authoring-with-instructions">AI-Led Test Authoring with Instructions</h2>
<p>One thing that has worked well for me is combining BicepConsoleTTK with AI-assisted test generation in VS Code.</p>
<p>I maintain a dedicated instructions file:</p>
<ul>
<li><a href="https://github.com/Andrew-D-Wilson/Bicep-Console-TTK/blob/main/bicepconsolettk.instructions.md">https://github.com/Andrew-D-Wilson/Bicep-Console-TTK/blob/main/bicepconsolettk.instructions.md</a></li>
</ul>
<p>This gives Copilot a clear contract for how tests should be authored for this toolkit, including:</p>
<ul>
<li>Correct <code>BeforeAll</code> module import patterns</li>
<li><code>Import-Bicep</code> usage for named, wildcard, and multi-file imports</li>
<li><code>Invoke-BicepExpression</code> patterns with aliases and setup declarations</li>
<li>Expected result formatting for strings, objects, arrays, and error assertions</li>
<li>REPL limitations to avoid (for example deployment-time functions)</li>
</ul>
<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>The result is faster test authoring while still staying aligned to the conventions and edge cases that matter for BicepConsoleTTK.</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>