TLDR; this article covers the testing framework Pester that you use to test your PowerShell scripts.
The reason you want to have tests are many:
There are many other reasons for wanting to have tests but the three above are quite compelling.
Pester is a test framework meant for PowerShell and is a module you can install. It has several features:
To install Pester, you run the below command.
Install-Module -Name Pester -Force
Once it's installed, you can start authoring your tests.
From install page:
Pester runs on Windows, Linux, MacOS and anywhere else thanks to PowerShell. It is compatible with Windows PowerShell 3, 4, 5, 6 and 7.
Pester 3 comes pre-installed with Windows 10
For our first test, we will learn how to write a test as well as running it.
Describe "A suite" {
It "my first test" {
$Value = "Value"
$Value | Should -Be "Value"
}
}
The test above, have a Describe
construct which is the declaration of a suite, and a string argument, giving the suite a name. Within the suite there's a test definition It
, which also has a string argument that represents the name of the test. Within the test, there's test itself where the code is set up:
$Value = "Value"
and then it's asserted upon
$Value | Should -Be "Value"
Note the use of the Should -Be
, which determines equality between $Value
and "Value".
Invoke-Pester
(./ for the path in Linux ans macOS and .\ for Windows): Invoke-Pester ./A-Test.ps1
The outcome of running the test is:
Starting discovery in 1 files.
Discovery found 1 tests in 6ms.
Running tests.
[+] /<path>/A-Test.ps1 42ms (11ms|26ms)
Tests completed in 44ms
Tests Passed: 1, Failed: 0, Skipped: 0 NotRun: 0
The first test was great in that it let us understand the mechanics of testing and concepts like a suite, a test, and an assertion like Should -Be
. A more real looking test would test code in a script file that's not in our test file. Here's the steps we will take next:
You will have code that you want to test but let's create this script file for the sake of demonstration.
Function Get-Tomato() {
new-object psobject -property @{ Name = "Tomato" }
}
This code will create a custom object for each time the Get-Tomato()
function is invoked.
. ./Get-Tomato.ps1
Get-Tomato
you should see the following in the console:
Name ----
Tomato
Now that we have our production code, let's author the test next.
Describe "Tomatoes" {
It "Get Tomato" {
$tomato = Get-Tomato
$tomato.Name | Should -Be "Tomato"
}
}
Invoke-Pester
:
Invoke-Pester ./Get-Tomato.Tests.ps1
you should see the following output:
Invoke-Pester ./Get-Tomato.Tests.ps1
Starting discovery in 1 files. Discovery found 1 tests in 6ms.
Running tests.
[+] /<path>/Get-Tomato.Tests.ps1 66ms (9ms|52ms)
Tests completed in 68ms
Tests Passed: 1, Failed: 0, Skipped: 0 NotRun: 0
Great, you ran a test on a more real looking code.
You will have code that you write that eventually performs side effects, like accessing a network resource or create a file. Let's look at such a case and how Pester handles it. The short answer is that you can use mocks, a construct that's executed instead of the actual command. All you need to do is to focus that the right behavior happens.
So, in this case, imagine that our production code now will have more function Save-Tomato
that saves an object to a file.
Function Save-Tomato() {
Param(
[string] $Name
)
New-Item -ItemType File -Path ./Tomato.txt -Value $Name -Force
}
. /Get-Tomato.ps1
Ok, so you have added Save-Tomato()
to your script file. Now for a test. Your code is calling New-Item
, which creates a new file. As part of testing, you don't want it to create a file each time the test is being run. More likely, you just want to see the test does what it's supposed to, i.e. calling the correct command/s. So, to solve this issue, we can mock, replace the current implementation of New-Item
with our own.
To mock, we first need to replace the actual implementation like so:
Mock -CommandName New-Item -MockWith {}
Save-Tomato()
:
Save-Tomato # this should call New-Item
The command Should -Invoke
, allows you to specify what command it should have called, like so:
Should -Invoke -CommandName New-Item -Times 1 -Exactly
The above code verifies New-Item
is called exactly one time.
It "Save tomato" {
Mock -CommandName New-Item -MockWith {}
Save-Tomato "my tomato"
Should -Invoke -CommandName New-Item -Times 1 -Exactly
}
Invoke-Pester
like so:
Invoke-Pester ./Get-Tomato.Tests.ps1
At this point your tests should run successfully like so:
Starting discovery in 1 files.
Discovery found 2 tests in 8ms.
Running tests.
[+] /Users/chnoring/Documents/dev/projects/powershell-projects/articles/Get-Tomato.Tests.ps1 78ms (34ms|37ms)
Tests completed in 80ms
Tests Passed: 2, Failed: 0, Skipped: 0 NotRun: 0
Also, note how Tomato.txt isn't created, because you are mocking the call to New-Item
, success.
Congrats, you've learned how to install test framework Pester, on top of that, you've learned to author your first tests and even learned how to mock the call to actual commands. To learn more, have a look at Pesters GitHub page:
- Pester GH page
- Check out the Pester extension for VS Code , thanks JGrote
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.