Skip to main content
Version: v5

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.

Get-Emoji.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
}
Get-Emoji.Tests.ps1
BeforeAll {
. $PSCommandPath.Replace('.Tests.ps1','.ps1')
}

Describe "Get-Emoji" {
It "Returns 🌵 (cactus)" {
Get-Emoji -Name cactus | Should -Be '🌵'
}
}
warning

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.

Get-Emoji.Tests.ps1
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.

Describe vs. Context

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.

Get-Emoji.Tests.ps1
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.