tl;dr: Here is a summary.
Pester is a testing and mocking framework for PowerShell.
Pester provides a framework for writing and running tests. Pester is most commonly used for writing unit and integration tests, but it is not limited to just that. It is also a base for tools that validate whole environments, computer deployments, database configurations and so on.
Pester follows a file naming convention
*.Tests.ps1, and uses a simple set of functions:
Mock to create a mini-DSL for writing your tests.
Pester tests can execute any command or script that is accessible to a Pester test file. This includes functions, Cmdlets, Modules and scripts. Pester can be run locally, where it integrates well with Visual Studio Code, and it can of course be integrated into a build script in a CI pipeline.
Pester contains a powerful set of Mocking capabilities that allow tests to replace the behavior of any command inside of a piece of PowerShell code being tested. See Mocking with Pester.
To install Pester it is usually enough to just do
Install-Module Pester -Force. And then follow it by
Import-Module Pester -PassThru. This is the output you should see in the console:
Full installation guide is available in installation.
To start using Pester, create a new file called
Get-Planet is the name of the function
we will be testing. Feel free to replace that with your own function name. The file name is important because Pester
uses a naming convention, all
*.Tests.ps1 files will be inspected for tests.
Inside of the file paste this code:
This code uses multiple Pester keywords, and we will go over them in detail soon, but for now let's just run it.
In your console run
Invoke-Pester -Output Detailed C:\t\Planets\Get-Planet.Tests.ps1:
Looking at the last line of output you can see that we run 1 test and it Passed. Good job, you just run your first Pester test! 🥳🥳🥳
In the previous run, our test passed, and if you'd run it again it would pass again. That is the beauty of automated testing. This is because using the
Should keyword we are saying:
👉 "There should be 8 items in
And there are.
But how did we know that we want to test for exactly that? Well, we didn't. It was just one example of how we could describe our Solar System. You can try remembering some facts about it and try writing them as tests.
Here are few examples:
- Earth is the third planet in our Solar System.
- Pluto is not part of our Solar System.
- The planets go in this order: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune.
Try adding those tests into your
Get-Planet.Tests.ps1 file. Put them under the other
It block, but make sure they are placed within the curly braces associated with
There are few ways to break the test, one of them is adding Pluto back into our Solar System.
This will break the assertion that we have in our test, because we no longer return 8 items from the tested function. Instead we now return 9. Running the test, it will no longer pass:
The error is:
Expected 8, but got 9., this exactly reflects the change that we made to the tested function. We added one more item to the collection of planets, and the test confirms that the function is now broken.
The change that we just did is not the only change that we can make to break the test. There are other ways to do it. We can change the expected count to be 1, saying that there is just one planet orbiting the Sun, by changing the
$allPlanets.Count | Should -Be 1. This will also break the test:
The error is:
Expected 1, but got 8., this again reflects exactly what we did in the test, but it no longer reflects the real world.
If you look closer on how we broke the test, you can see that there are two distinct ways to break it.
- The first was that the tested function did not work correctly, this is a good way to break the test.
- The second one is when the function works correctly, but the test is incorrect. This is a bad way to break the test.
Being able to distinguish between those two is important, when your test breaks keep in mind that either the function, or the tests might be broken. What you usually do is that you look at what changed more recently. If the test is new, and the function existed for a while, you first blame the test. When the test was passing before, but it is not anymore, you first blame the tested function.
Now that we know about
Should. We can quickly look at the rest of the Pester keywords that we used.
This keyword allows you to group tests (represented by
It blocks) into groups. You can have one or more
Describes per file. You can also nest
Describes into each other to give your test suite more structure.
A similar keyword to
Context. In almost all cases
Context can be used interchangeably with
Describe. Typically the top-level block is a
Describe that is named after the function that is being tested. And then, if needed,
Context blocks are used inside of the
Describe to group tests based on what aspect of the function you are testing.
Now that we split the tests into groups we might want to share some common code among those tests. To do this we
BeforeAll block that will run at the start of the block that contains it, or at the start of the file if not contained in any block. In our example we used it to define the tested function.
There is also
AfterAll block that will run at the end of the block, and
BeforeEach / AfterEach that will run before every test in the given block.
Until now we had just a single file that contained both our tests and the function that's being tested. In real life you want the tested function to be separated from its tests. This way you can run the function, without running the tests with it.
To move the function out of the test we will move it to a separate file, and will dot-source it back into the
BeforeAll. To do this create a new file in the same directory as
Get-Planet.Tests.ps1 and call it
Get-Planet.ps1. Then cut the function from the test file and paste it into the other file:
If we now run the test, we will see that it breaks, because the function is no longer reachable from the test:
The error is
CommandNotFoundException: The term 'Get-Planet' is not recognized as the name of a cmdlet, function, script file, or operable program.. This is because the function is not defined, and we need to make it available to the test.
🤷♀ If your test still works, try starting a clean PowerShell session, chances are, you played around with the code, and the tested function is still defined in your scope. Starting a new PowerShell window will clean that up.
To get the function back to scope we will dot-source (import) the file inside of
Pester uses a file naming convention
*.Tests.ps1 for test files. Those files are typically named after the tested function, and are placed next to a file that contains the function. The function file is imported via dot-sourcing in the
BeforeAll on top of the file. And
$PSScriptRoot is typically used to provide a relative path to the function file.
Tests are written into
It blocks and grouped by
Context into groups.
Should is used to express what is being tested, and it will fail the test if the condition is not true.
Invoke-Pester can then be used to run the tests in a given test file, and
-Output Detailed can be used to show every test in the output, no matter if it passed or failed. Otherwise only failed tests, or whole files (when everything passed) are shown.
To learn more, for example how to run multiple test files in one run, continue to the Usage section.