A Guide to the Pester DSL
So you’ve decided to start testing your PowerShell code with Pester. That’s a fantastic step towards writing more reliable automation! When you first look at a Pester test file (.Tests.ps1
), you’ll see a structure that looks like PowerShell but has its own special vocabulary. This is Pester’s Domain-Specific Language (DSL).
Understanding this DSL is the key to unlocking Pester’s power. Let’s break down the anatomy of a Pester test, block by block.
Describe
: The Outermost Container
The Describe
block is the top-level container for your tests. Its purpose is to group all the tests related to a single function or script. The name you give the Describe
block should be the name of the function you are testing.
Describe 'Get-FormattedDate' {
# All tests for Get-FormattedDate go here
}
Context
: Grouping Related Scenarios
Inside a Describe
block, you can use one or more Context
blocks. Context
is used to group a set of tests for a specific scenario or condition. This helps organize your tests and make them more readable.
For example, you might have one context for when your function receives valid input and another for when it receives invalid input.
Describe 'Get-FormattedDate' {
Context 'When given a valid date' {
# Tests for the happy path go here
}
Context 'When given an invalid date' {
# Tests for error handling go here
}
}
It
: The Individual Test Case
The It
block is the heart of your test. It represents a single, specific test case that verifies one piece of behavior. The name of the It
block should be a human-readable sentence that describes what the function should do.
An It
block contains the code to run your function and an assertion to check the result.
It 'Should return the date in YYYY-MM-DD format' {
$date = Get-Date '2023-10-31'
$result = Get-FormattedDate -Date $date
$result | Should -Be '2023-10-31'
}
Setup and Teardown Blocks
Pester provides special blocks to run code before and after your tests. This is perfect for setting up prerequisites (like creating temporary files or mocking commands) and cleaning up afterward.
BeforeAll
/AfterAll
: These run once perDescribe
orContext
block. Use them for setup/teardown that is expensive and can be shared across all tests in that block.BeforeEach
/AfterEach
: These run before and after every singleIt
block. Use them to ensure each test starts from a clean, isolated state.
Should
: The Assertion
The Should
command is Pester’s assertion engine. It’s how you check if the actual output of your code matches the expected output. It uses a natural language syntax that is easy to read.
$result | Should -Be 'ExpectedValue'
(for simple values)$result | Should -BeNullOrEmpty
(to check for null or empty output){ My-Function } | Should -Throw 'Error message'
(to check for errors)
Pester has a rich set of assertion operators for almost any scenario you can imagine.
Putting It All Together
Here’s a complete example showing how all these pieces fit together to test a simple function.
Describe 'Get-FileUpperCase' {
# Runs once before any tests in this block
BeforeAll {
# Create a temporary directory for test files
$script:tempDir = New-Item -Path (Join-Path $env:TEMP ([System.Guid]::NewGuid())) -ItemType Directory
}
# Runs once after all tests in this block are complete
AfterAll {
# Clean up the temporary directory
Remove-Item -Path $script:tempDir -Recurse -Force
}
Context 'When the file exists' {
# Runs before each 'It' block in this Context
BeforeEach {
$script:testFile = Join-Path $script:tempDir.FullName -ChildPath 'test.txt'
Set-Content -Path $script:testFile -Value "line one`nline two"
}
It 'Should return the content in upper case' {
$result = Get-FileUpperCase -Path $script:testFile
$result | Should -Be @('LINE ONE', 'LINE TWO')
}
}
Context 'When the file does not exist' {
It 'Should throw an error' {
# The code to be tested is wrapped in a script block
{ Get-FileUpperCase -Path 'nonexistent.txt' } | Should -Throw 'File not found*'
}
}
}
By organizing your tests with this structure, you create a specification for your code that is not only automated but also serves as living documentation. Anyone can read your Pester tests and understand exactly what your code is designed to do and how it should handle different situations.