Unit-testing PS help
It’s really useful to have someone to keep you honest if you don’t write help for your PS functions as you go. I find it hard to update help continually, so a red test makes sure I don’t check in code with no help.
Pester tests for PS function-based help
#Help.Tests.ps1
Describe 'Function help' {
$ExportedCommands = (
Get-Module $ModuleName |
select -ExpandProperty ExportedCommands
).Keys
Context 'Correctly-formatted help' {
It 'Provides examples for every exported function' {
foreach ($Command in $ExportedCommands)
{
(Get-Help $Command -Examples |
Out-String) |
Should Match '-------------------------- EXAMPLE 1 --------------------------'
}
}
}
}
It literally does text matching on the output of Get-Help piped to Out-String. Not very powershelly! Perhaps it should use grep.
It would be tough to write a unit test to ensure the help is actually useful. But this will validate that all the examples are syntactically correct:
#Help.Tests.ps1
$ModuleName = 'FunkyModule'
Import-Module "$PSScriptRoot\$ModuleName.psm1" -Force
Describe 'Function help' {
Context 'Correctly-formatted help' {
foreach (
$Command in (
Get-Module $ModuleName |
select -ExpandProperty ExportedCommands
).Keys
)
{
$Help = Get-Help $Command
It "$Command has one or more help examples" {
$Help.examples.example | Should Not Be $null
}
#Test only the parameters? Mock it and see if it throws
Mock $Command -MockWith {}
It "$Command examples are syntactically correct" {
foreach ($Example in $Help.examples.example) {
[Scriptblock]::Create($Example.code) |
Should Not Throw
}
}
} #end foreach
}
}
Get-Help returns an object that parses the code example out of the rest of the example text. Unfortunately, it only recognises a single line of code. The following will not properly test the code, because the .code property will be only the $MyVar line:
<#
.Example
PS C:\> $MyVar = 'Djibouti', 'Fiji'
PS C:\> Get-CapitalCity -Country $MyVar
Gets the capital city
#>
Testing the syntax
Pester mocking transparently recreates the param block of the command you are mocking, so, to save handling the running of the command, we just run an empty mock of it:
#Test only the parameters? Mock it and see if it throws
Mock $Command -MockWith {}
If we run the tests on a module like this:
**#FunkyModule.psm1**
function Funk {
<#
.Example
PS C:\> Funk -Disco
.Example
PS C:\> Funk -On 1, 2, 3, 4
some helpful text
#>
[CmdletBinding()]
param(
[switch]$Disco,
[int[]]$On
)
Write-Host "Lorem ipsum"
}
The object form the Get-Help pulls out the example commands from the function help:
PS C:\> foreach ($Example in $Help.examples.example) {
[Scriptblock]::Create($Example.code)
}
Funk -Disco
Funk -On 1, 2, 3, 4
This matches the param block:
param(
[switch]$Disco,
[int[]]$On
)
Therefore this test passes. But if we change or delete one of the parameter declarations so the examples are no longer correct, the test fails:
PS C:\> .\dev\FunkyModule\Help.Tests.ps1
Describing Function help
Context Correctly-formatted help
[+] Funk has one or more help examples 45ms
[-] Funk examples are syntactically correct 46ms
Expected: the expression not to throw an exception. Message was {A parameter cannot be found that matches parameter name 'Disco'.}
from line:1 char:6
+ Funk -Disco
+ ~~~~~~
32: [Scriptblock]::Create($Invocation) | Should Not Throw
at , C:\dev\FunkyModule\Help.Tests.ps1: line 32
The message it gives you tells you how you borked it:
Message was {A parameter cannot be found that matches parameter name 'Disco'.}
So there you have it - unit testing for Powershell function help.