The other day I shared my PowerShell plans for 2022. And needless to say, I didn't wait to dig in. I am working on a new module and since it won't be published until next month, I went ahead and marked it as Core only. I also started writing a set of Pester 5.x tests for it. Naturally, this is forcing me to revisit the Pester documentation and re-learn how to construct a Pester test.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
During this process, I decided I needed to help myself speed up the test writing phase. I have a standard set of tests that I like to use for functions in my module. But copying and pasting code snippets is tedious. I know I could create a set of VS Code snippets, but that feels limiting and I'd have to make sure the snippets are available on all systems where I might be running VS Code. Instead, I wrote a PowerShell function to accelerate developing Pester 5.x tests.
My function takes a module and extracts all of the public exported functions. For each function, it creates a set of standard Pester assertions. These are the baseline or boilerplate tests that I always want to run for each function. Each function is wrapped in a Describe block. Although, I can opt for a Context block instead. This command will also insert tags. Note that my code for the tag insertion relies on the ternary operator from PowerShell 7.
$($tags ? "-tag $($tags -join ',')" : $null)
If you want to use my function in Windows PowerShell, you'll need to revise it.
The function writes strings to the pipeline.
I can run New-PesterBlock and pipe to Out-File or Set-Clipboard.
Here's the script file with the function.
#requires -version 7.1
Function New-PesterBlock {
<#
.SYNOPSIS
Create a Pester 5.x test block
.DESCRIPTION
Create a Pester test block for exported functions from a given module.
The default output is a Describe block for each function but you can
create a Context block as an alternative.
The default behavior is to add a 'Function' tag to each block. You
can specify your own comma separated list of tags. Or use a parameter
value of $Null to not insert any tags.
The output is written for Pester 5.x.
.EXAMPLE
PS C:\> New-PesterBlock psteachingtools
Describe Get-Vegetable {
It "Should have help documentation" {
(Get-Help Get-Vegetable).Description | Should -Not -BeNullOrEmpty
}
It "Should have a defined output type" {
(Get-Command -CommandType function -name Get-Vegetable).OutputType | Should -not -BeNullOrEmpty
}
It "Should run without error" {
#mock and set mandator parameters as needed
{Get-Vegetable} | Should -Not -Throw
} -pending
} -tag function
Describe New-Vegetable {
It "Should have help documentation" {
(Get-Help New-Vegetable).Description | Should -Not -BeNullOrEmpty
}
It "Should have a defined output type" {
(Get-Command -CommandType function -name New-Vegetable).OutputType | Should -not -BeNullOrEmpty
}
It "Should run without error" {
#mock and set mandator parameters as needed
{New-Vegetable} | Should -Not -Throw
} -pending
} -tag function
...
Create a Describe block for each function in the PSTeachingTools module.
. EXAMPLE
PS C:\> New-Pesterblock psteachingtools -BlockType Context | Set-Clipboard
Create a context block for each function and copy the output to the Windows clipboard.
.INPUTS
None
.OUTPUTS
[System.String]
#>
[cmdletbinding()]
[alias("npb")]
[OutputType([System.String])]
Param(
[Parameter(Position = 0, Mandatory,HelpMessage = "The name of a PowerShell module.")]
[ValidateNotNullOrEmpty()]
[string]$ModuleName,
[Parameter(Position = 1,HelpMessage = "What kind of Pester test block do you want to create?")]
[ValidateSet("Describe", "Context")]
[string]$BlockType = "Describe",
[Parameter(HelpMessage = "Specify tags separated by commas. Use `$null to not insert any tags.")]
[string[]]$Tag = "function"
)
Begin {
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"
#Put blocktype in proper case to make it pretty
$BlockType = [System.Globalization.CultureInfo]::CurrentUICulture.TextInfo.ToTitleCase($BlockType)
$Tags = $tag -join ","
} #begin
Process {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting commands from $ModuleName"
Try {
$cmds = Get-Command -Module $ModuleName -CommandType Function -ErrorAction Stop
}
Catch {
Throw $_
}
if ($cmds) {
Foreach ($cmd in $cmds) {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Defining tests for $($cmd.name)"
@"
$BlockType $($cmd.name) {
It "Should have help documentation" {
(Get-Help $($cmd.name)).Description | Should -Not -BeNullOrEmpty
}
It "Should have a defined output type" {
(Get-Command -CommandType function -name $($cmd.name)).OutputType | Should -Not -BeNullOrEmpty
}
It "Should run without error" {
<#
mock and set mandatory parameters as needed
this test is marked as pending since it
most likely needs to be refined
#>
{$($cmd.name)} | Should -Not -Throw
} -pending
#insert additional command-specific tests
} $($tags ? "-tag $($tags -join ',')" : $null)
"@
} #foreach cmd
} #if cmds
else {
Write-Warning "No functions found in the module $Modulename or the module itself doesn't exist."
}
} #process
End {
Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"
} #end
} #close New-PesterBlock
The function creates a here-string for each test block. These are my default assertions. You might want to edit your version of the function. This command is something I might integrate into a VS Code task. I hope you'll let me know how you end up using it.
I wish here string were not such a wart on the nose syntax item.