Test file structure
Pester test files follow a similar structure. Usually there is some setup to get the tested function imported and a top-level Describe
block to hold all the tests and child blocks. Inside that Describe
we will find the tests themselves represented by It
blocks. Those blocks are often grouped into their own Describe
or Context
based on the area of code they are testing. Those groups, can each have their own setups and teardowns represented by BeforeAll
, BeforeEach
, AfterEach
and AfterAll
blocks.
Simple test file
This test file is a simple as it can be. We have a BeforeAll
setup placed at the top of the file to import the tested function from its own file. Followed by a top-level Describe
block called "Get-Emoji" because that is the function we are testing. Inside of that Describe
block we have a single test represented by an It
block. This block will run and will either pass or fail to represent the result of the test.
Required code for samples (PowerShell 6 or later)
To run the sample below, place this file in the same folder as Get-Emoji.Tests.ps1.
$emojis = @(
@{ Name = 'apple'; Symbol = '🍎'; Kind = 'Fruit' }
@{ Name = 'beaming face with smiling eyes'; Symbol = '😁'; Kind = 'Face' }
@{ Name = 'cactus'; Symbol = '🌵'; Kind = 'Plant' }
@{ Name = 'giraffe'; Symbol = '🦒'; Kind = 'Animal' }
@{ Name = 'pencil'; Symbol = '✏️'; Kind = 'Item' }
@{ Name = 'penguin'; Symbol = '🐧'; Kind = 'Animal' }
@{ Name = 'pensive'; Symbol = '😔'; Kind = 'Face' }
@{ Name = 'slightly smiling face'; Symbol = '🙂'; Kind = 'Face' }
@{ Name = 'smiling face with smiling eyes'; Symbol = '😊'; Kind = 'Face' }
) | ForEach-Object { [PSCustomObject]$_ }
function Get-Emoji ([string]$Name = '*') {
$emojis | Where-Object Name -like $Name | ForEach-Object Symbol
}
BeforeAll {
. $PSCommandPath.Replace('.Tests.ps1','.ps1')
}
Describe "Get-Emoji" {
It "Returns 🌵 (cactus)" {
Get-Emoji -Name cactus | Should -Be '🌵'
}
}
Put all your code into It
, BeforeAll
, BeforeEach
, AfterAll
or AfterEach
. Put no code directly into Describe
, Context
or on the top of your file, without wrapping it in one of these blocks.
Any code outside one of these blocks will run during Discovery, and the results won't be available during Run which will lead to confusing results.
See Discovery and Run.
More complex file
In this file we are using more features of Pester and organize our tests into more groups based on what we are testing. Each group is represented by a Describe
or Context
block. Within those groups we are specifying distinct setups represented by BeforeAll
that will run at the start of the Describe
(or Context
) that contains them. Specifically we use the setup to run the tested function once per group of tests to ensure that the whole group uses the same filter. This avoids typos and copy paste errors.
BeforeAll {
. $PSCommandPath.Replace('.Tests.ps1','.ps1')
}
Describe "Get-Emoji" {
Context "Lookup by whole name" {
It "Returns 🌵 (cactus)" {
Get-Emoji -Name cactus | Should -Be '🌵'
}
It "Returns 🦒 (giraffe)" {
Get-Emoji -Name giraffe | Should -Be '🦒'
}
}
Context "Lookup by wildcard" {
Context "by prefix" {
BeforeAll {
$emojis = Get-Emoji -Name pen*
}
It "Returns ✏️ (pencil)" {
$emojis | Should -Contain "✏️"
}
It "Returns 🐧 (penguin)" {
$emojis | Should -Contain "🐧"
}
It "Returns 😔 (pensive)" {
$emojis | Should -Contain "😔"
}
}
Context "by contains" {
BeforeAll {
$emojis = Get-Emoji -Name *smiling*
}
It "Returns 🙂 (slightly smiling face)" {
$emojis | Should -Contain "🙂"
}
It "Returns 😁 (beaming face with smiling eyes)" {
$emojis | Should -Contain "😁"
}
It "Returns 😊 (smiling face with smiling eyes)" {
$emojis | Should -Contain "😊"
}
}
}
}
Each of the groups could also use an AfterAll
block which would clean up after the tests. This is referred to as teardown. The AfterAll
block is guaranteed to run even if tests fail.
Additionally we could also have a BeforeEach
or AfterEach
block. Those blocks work just like the *All
blocks, but the code would run before and after each It
. See Setup and teardown for more info.
In almost all cases it does not matter if you use Describe
or Context
. They behave the same and are the same function internally. There are only two places where we distinguish them:
- On
Mock
when-Scope Describe
or-Scope Context
is used. - In output, when the block information is written to screen.
More complex file with test cases
The file below can take advantage of TestCases
(which are also aliased -ForEach
) to repeat the It
block once for every item in the provided array. This makes the It
code repeat less, and makes it very easy to add new examples.
BeforeAll {
. $PSCommandPath.Replace('.Tests.ps1','.ps1')
}
Describe "Get-Emoji" {
Context "Lookup by whole name" {
It "Returns <expected> (<name>)" -TestCases @(
@{ Name = "cactus"; Expected = '🌵'}
@{ Name = "giraffe"; Expected = '🦒'}
) {
Get-Emoji -Name $name | Should -Be $expected
}
}
Context "Lookup by wildcard" {
Context "by prefix" {
BeforeAll {
$emojis = Get-Emoji -Name pen*
}
It "Returns <expected> (<name>)" -TestCases @(
@{ Name = "pencil"; Expected = '✏️'}
@{ Name = "penguin"; Expected = '🐧'}
@{ Name = "pensive"; Expected = '😔'}
) {
$emojis | Should -Contain $expected
}
}
Context "by contains" {
BeforeAll {
$emojis = Get-Emoji -Name *smiling*
}
It "Returns <expected> (<name>)" -TestCases @(
@{ Name = "slightly smiling face"; Expected = '🙂'}
@{ Name = "beaming face with smiling eyes"; Expected = '😁'}
@{ Name = "smiling face with smiling eyes"; Expected = '😊'}
) {
$emojis | Should -Contain $expected
}
}
}
}
See Data driven tests.
Even more complex file
With the example above we are still far away from a really complex test file. And that is good. The simpler you are able to keep your tests the better. The more static your tests are, the easier it is to feel confident that everything works.
You for example might notice that "by prefix" and "by contains" are very similar, and you would be able to generate them by re-using the same Context
by just providing a different value to -Name
. While this is technically possible using the techniques below, I would not do it. The level of complexity shown in "More complex file with test cases" is about right for 90% of tests I write.
Take the examples below as a list of things you might need when you can't avoid them. Not as a list of things that you need to use in all of your tests to get "Advanced Pester user" achievement unlocked. Less is more.
Test script parameters
Passing parameters using the PowerShell param()
block to provide external data to the .Tests.ps1
file:
param (
$File
)
Describe "File <file> is not empty" {
# ...
}
See Data driven tests.
Expanding data to generate Describe
and Context
blocks
Much like on It
, You can use -ForEach
on Describe
and Context
to repeat it for every item in the provided array:
BeforeDiscovery {
$files = @(
"Get-Emoji.ps1",
"Get-Planet.ps1"
)
}
Describe "function <_> has help" -ForEach $files {
# ....
}
This can be paired with the test script parameters to generate tests based on external data.
See Data driven tests.
BeforeDiscovery
In Pester5 the mantra is to put all code in Pester controlled blocks. No code should be directly in the script, or directly in Describe
or Context
block without wrapping it in some other block.
There is a special block called BeforeDiscovery
that shows intent when you need to put code directly in the script, or directly in Describe
or Context
block. This is useful when Data driven tests.
See Data driven tests.